From c86f2c045e3cc614e5f68d84b5055a1b0e222f67 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Wed, 3 May 2023 01:57:32 +0530 Subject: [PATCH] feat(connector): [ACI] Add banking redirect support for EPS, Giropay, iDEAL, and Sofortueberweisung (#890) Co-authored-by: Arjun Karthik --- crates/api_models/src/payments.rs | 3 + .../router/src/connector/aci/result_codes.rs | 61 +++++++- .../router/src/connector/aci/transformers.rs | 138 ++++++++++++++++-- crates/router/src/connector/adyen.rs | 6 +- .../src/connector/stripe/transformers.rs | 4 +- crates/router/src/types.rs | 1 + 6 files changed, 194 insertions(+), 19 deletions(-) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b9124844a6..4406a3b584 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -593,6 +593,9 @@ pub enum BankRedirectData { Giropay { /// The billing details for bank redirection billing_details: BankRedirectBilling, + /// Bank account details for Giropay + bank_account_bic: Option>, + bank_account_iban: Option>, }, Ideal { /// The billing details for bank redirection diff --git a/crates/router/src/connector/aci/result_codes.rs b/crates/router/src/connector/aci/result_codes.rs index 4ef07a29bb..1fae407964 100644 --- a/crates/router/src/connector/aci/result_codes.rs +++ b/crates/router/src/connector/aci/result_codes.rs @@ -1,4 +1,4 @@ -pub(super) const FAILURE_CODES: [&str; 440] = [ +pub(super) const FAILURE_CODES: [&str; 499] = [ "100.370.100", "100.370.110", "100.370.111", @@ -439,6 +439,65 @@ pub(super) const FAILURE_CODES: [&str; 440] = [ "100.380.201", "100.380.305", "100.380.306", + "800.100.100", + "800.100.150", + "800.100.151", + "800.100.152", + "800.100.153", + "800.100.154", + "800.100.155", + "800.100.156", + "800.100.157", + "800.100.158", + "800.100.159", + "800.100.160", + "800.100.161", + "800.100.162", + "800.100.163", + "800.100.164", + "800.100.165", + "800.100.166", + "800.100.167", + "800.100.168", + "800.100.169", + "800.100.170", + "800.100.171", + "800.100.172", + "800.100.173", + "800.100.174", + "800.100.175", + "800.100.176", + "800.100.177", + "800.100.178", + "800.100.179", + "800.100.190", + "800.100.191", + "800.100.192", + "800.100.195", + "800.100.196", + "800.100.197", + "800.100.198", + "800.100.199", + "800.100.200", + "800.100.201", + "800.100.202", + "800.100.203", + "800.100.204", + "800.100.205", + "800.100.206", + "800.100.207", + "800.100.208", + "800.100.402", + "800.100.403", + "800.100.500", + "800.100.501", + "800.700.100", + "800.700.101", + "800.700.201", + "800.700.500", + "800.800.102", + "800.800.202", + "800.800.302", ]; pub(super) const SUCCESSFUL_CODES: [&str; 16] = [ diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index 010de4d91a..3404cd7c28 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -2,11 +2,13 @@ use std::str::FromStr; use error_stack::report; use masking::Secret; +use reqwest::Url; use serde::{Deserialize, Serialize}; use super::result_codes::{FAILURE_CODES, PENDING_CODES, SUCCESSFUL_CODES}; use crate::{ core::errors, + services, types::{self, api, storage::enums}, }; @@ -47,16 +49,39 @@ pub struct AciCancelRequest { pub payment_type: AciPaymentType, } -#[derive(Debug, Clone, Eq, PartialEq, Serialize)] +#[derive(Debug, Clone, Serialize)] #[serde(untagged)] pub enum PaymentDetails { #[serde(rename = "card")] - Card(CardDetails), + AciCard(Box), + BankRedirect(Box), #[serde(rename = "bank")] Wallet, Klarna, - #[serde(rename = "bankRedirect")] - BankRedirect, +} + +#[derive(Debug, Clone, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct BankRedirectionPMData { + payment_brand: PaymentBrand, + #[serde(rename = "bankAccount.country")] + bank_account_country: Option, + #[serde(rename = "bankAccount.bankName")] + bank_account_bank_name: Option, + #[serde(rename = "bankAccount.bic")] + bank_account_bic: Option>, + #[serde(rename = "bankAccount.iban")] + bank_account_iban: Option>, + shopper_result_url: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum PaymentBrand { + Eps, + Ideal, + Giropay, + Sofortueberweisung, } #[derive(Debug, Clone, Eq, PartialEq, Serialize)] @@ -101,16 +126,64 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for AciPaymentsRequest { type Error = error_stack::Report; fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { let payment_details: PaymentDetails = match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(ccard) => PaymentDetails::Card(CardDetails { + api::PaymentMethodData::Card(ccard) => PaymentDetails::AciCard(Box::new(CardDetails { card_number: ccard.card_number, card_holder: ccard.card_holder_name, card_expiry_month: ccard.card_exp_month, card_expiry_year: ccard.card_exp_year, card_cvv: ccard.card_cvc, - }), + })), api::PaymentMethodData::PayLater(_) => PaymentDetails::Klarna, api::PaymentMethodData::Wallet(_) => PaymentDetails::Wallet, - api::PaymentMethodData::BankRedirect(_) => PaymentDetails::BankRedirect, + api::PaymentMethodData::BankRedirect(ref redirect_banking_data) => { + match redirect_banking_data { + api_models::payments::BankRedirectData::Eps { .. } => { + PaymentDetails::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Eps, + bank_account_country: Some(api_models::enums::CountryCode::AT), + bank_account_bank_name: None, + bank_account_bic: None, + bank_account_iban: None, + shopper_result_url: item.request.router_return_url.clone(), + })) + } + api_models::payments::BankRedirectData::Giropay { + bank_account_bic, + bank_account_iban, + .. + } => PaymentDetails::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Giropay, + bank_account_country: Some(api_models::enums::CountryCode::DE), + bank_account_bank_name: None, + bank_account_bic: bank_account_bic.clone(), + bank_account_iban: bank_account_iban.clone(), + shopper_result_url: item.request.router_return_url.clone(), + })), + api_models::payments::BankRedirectData::Ideal { bank_name, .. } => { + PaymentDetails::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Ideal, + bank_account_country: Some(api_models::enums::CountryCode::NL), + bank_account_bank_name: Some(bank_name.to_string()), + bank_account_bic: None, + bank_account_iban: None, + shopper_result_url: item.request.router_return_url.clone(), + })) + } + api_models::payments::BankRedirectData::Sofort { country, .. } => { + PaymentDetails::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Sofortueberweisung, + bank_account_country: Some(*country), + bank_account_bank_name: None, + bank_account_bic: None, + bank_account_iban: None, + shopper_result_url: item.request.router_return_url.clone(), + })) + } + _ => Err(errors::ConnectorError::NotImplemented( + "Payment method".to_string(), + ))?, + } + } api::PaymentMethodData::Crypto(_) | api::PaymentMethodData::BankDebit(_) => { Err(errors::ConnectorError::NotSupported { payment_method: format!("{:?}", item.payment_method), @@ -152,6 +225,7 @@ pub enum AciPaymentStatus { Failed, #[default] Pending, + RedirectShopper, } impl From for enums::AttemptStatus { @@ -160,6 +234,7 @@ impl From for enums::AttemptStatus { AciPaymentStatus::Succeeded => Self::Charged, AciPaymentStatus::Failed => Self::Failure, AciPaymentStatus::Pending => Self::Authorizing, + AciPaymentStatus::RedirectShopper => Self::AuthenticationPending, } } } @@ -180,7 +255,7 @@ impl FromStr for AciPaymentStatus { } } -#[derive(Default, Clone, Deserialize, PartialEq, Eq)] +#[derive(Debug, Default, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct AciPaymentsResponse { id: String, @@ -189,6 +264,21 @@ pub struct AciPaymentsResponse { timestamp: String, build_number: String, pub(super) result: ResultCode, + pub(super) redirect: Option, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct AciRedirectionData { + method: Option, + parameters: Vec, + url: Url, +} + +#[derive(Clone, Debug, Deserialize, PartialEq, Eq)] +pub struct Parameters { + name: String, + value: String, } #[derive(Default, Debug, Clone, Deserialize, PartialEq, Eq)] @@ -214,13 +304,37 @@ impl fn try_from( item: types::ResponseRouterData, ) -> Result { + let redirection_data = item.response.redirect.map(|data| { + let form_fields = std::collections::HashMap::<_, _>::from_iter( + data.parameters + .iter() + .map(|parameter| (parameter.name.clone(), parameter.value.clone())), + ); + + // If method is Get, parameters are appended to URL + // If method is post, we http Post the method to URL + services::RedirectForm::Form { + endpoint: data.url.to_string(), + // Handles method for Bank redirects currently. + // 3DS response have method within preconditions. That would require replacing below line with a function. + method: data.method.unwrap_or(services::Method::Post), + form_fields, + } + }); + Ok(Self { - status: enums::AttemptStatus::from(AciPaymentStatus::from_str( - &item.response.result.code, - )?), + status: { + if redirection_data.is_some() { + enums::AttemptStatus::from(AciPaymentStatus::RedirectShopper) + } else { + enums::AttemptStatus::from(AciPaymentStatus::from_str( + &item.response.result.code, + )?) + } + }, response: Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: None, + redirection_data, mandate_reference: None, connector_metadata: None, }), diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 31d3740bf5..70562ba1c4 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -375,11 +375,7 @@ impl fn build_request( &self, - req: &types::RouterData< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, + req: &types::PaymentsAuthorizeRouterData, connectors: &settings::Connectors, ) -> CustomResult, errors::ConnectorError> { Ok(Some( diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 6c355d52ee..a75c64b36a 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -571,7 +571,9 @@ impl TryFrom<&payments::BankRedirectData> for StripeBillingAddress { name: Some(billing_details.billing_name.clone()), ..Self::default() }), - payments::BankRedirectData::Giropay { billing_details } => Ok(Self { + payments::BankRedirectData::Giropay { + billing_details, .. + } => Ok(Self { name: Some(billing_details.billing_name.clone()), ..Self::default() }), diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 6b7850b6a8..2e08c68081 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -459,6 +459,7 @@ pub struct ConnectorResponse { pub return_url: Option, pub three_ds_form: Option, } + pub struct ResponseRouterData { pub response: R, pub data: RouterData,