From 75648262e7f741351c1149cd01083065d17bde7f Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Fri, 4 Oct 2024 15:06:14 +0530 Subject: [PATCH] fix(router): Persist card_network if present for non co-badged cards (#6212) Co-authored-by: AkshayaFoiger Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> --- Cargo.lock | 1 + crates/api_models/src/payments.rs | 24 +- crates/cards/Cargo.toml | 1 + crates/cards/src/validate.rs | 54 ++++- .../src/connector/adyen/transformers.rs | 64 ++++- .../connector/bankofamerica/transformers.rs | 23 +- .../src/connector/cybersource/transformers.rs | 71 ++++-- .../src/connector/stripe/transformers.rs | 34 +++ crates/router/src/consts.rs | 1 + crates/router/src/core/payments/helpers.rs | 221 ++++++++++-------- .../payments/operations/payment_confirm.rs | 19 +- .../payments/operations/payment_create.rs | 11 +- .../payments/operations/payment_update.rs | 6 +- 13 files changed, 383 insertions(+), 147 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d90c577152d0..cdab5a05c4bd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1846,6 +1846,7 @@ dependencies = [ "common_utils", "error-stack", "masking", + "regex", "router_env", "serde", "serde_json", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 9d606dacb9ff..99e2a1697524 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -8,6 +8,7 @@ use cards::CardNumber; use common_utils::{ consts::default_payments_list_limit, crypto, + errors::ValidationError, ext_traits::{ConfigExt, Encode, ValueExt}, hashing::HashedString, id_type, @@ -1396,8 +1397,11 @@ impl GetAddressFromPaymentMethodData for Card { } impl Card { - fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self { - Self { + fn apply_additional_card_info( + &self, + additional_card_info: AdditionalCardInfo, + ) -> Result> { + Ok(Self { card_number: self.card_number.clone(), card_exp_month: self.card_exp_month.clone(), card_exp_year: self.card_exp_year.clone(), @@ -1410,7 +1414,7 @@ impl Card { card_network: self .card_network .clone() - .or(additional_card_info.card_network), + .or(additional_card_info.card_network.clone()), card_type: self.card_type.clone().or(additional_card_info.card_type), card_issuing_country: self .card_issuing_country @@ -1418,7 +1422,7 @@ impl Card { .or(additional_card_info.card_issuing_country), bank_code: self.bank_code.clone().or(additional_card_info.bank_code), nick_name: self.nick_name.clone(), - } + }) } } @@ -1858,16 +1862,16 @@ impl PaymentMethodData { pub fn apply_additional_payment_data( &self, additional_payment_data: AdditionalPaymentData, - ) -> Self { + ) -> Result> { if let AdditionalPaymentData::Card(additional_card_info) = additional_payment_data { match self { - Self::Card(card) => { - Self::Card(card.apply_additional_card_info(*additional_card_info)) - } - _ => self.to_owned(), + Self::Card(card) => Ok(Self::Card( + card.apply_additional_card_info(*additional_card_info)?, + )), + _ => Ok(self.to_owned()), } } else { - self.to_owned() + Ok(self.to_owned()) } } diff --git a/crates/cards/Cargo.toml b/crates/cards/Cargo.toml index 1178568d72e2..b6b9f2b886fc 100644 --- a/crates/cards/Cargo.toml +++ b/crates/cards/Cargo.toml @@ -14,6 +14,7 @@ error-stack = "0.4.1" serde = { version = "1.0.197", features = ["derive"] } thiserror = "1.0.58" time = "0.3.35" +regex = "1.10.4" # First party crates common_utils = { version = "0.1.0", path = "../common_utils" } diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index 1af9bf582b0f..de4502698901 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -1,6 +1,10 @@ -use std::{fmt, ops::Deref, str::FromStr}; +use std::{collections::HashMap, fmt, ops::Deref, str::FromStr}; +use common_utils::errors::ValidationError; +use error_stack::report; use masking::{PeekInterface, Strategy, StrongSecret, WithType}; +use regex::Regex; +use router_env::once_cell::sync::Lazy; #[cfg(not(target_arch = "wasm32"))] use router_env::{logger, which as router_env_which, Env}; use serde::{Deserialize, Deserializer, Serialize}; @@ -46,6 +50,54 @@ impl CardNumber { .rev() .collect::() } + pub fn is_cobadged_card(&self) -> Result> { + /// Regex to identify card networks + static CARD_NETWORK_REGEX: Lazy>> = Lazy::new( + || { + let mut map = HashMap::new(); + map.insert("Mastercard", Regex::new(r"^(5[1-5][0-9]{14}|2(2(2[1-9]|[3-9][0-9])|[3-6][0-9][0-9]|7([0-1][0-9]|20))[0-9]{12})$")); + map.insert("American Express", Regex::new(r"^3[47][0-9]{13}$")); + map.insert("Visa", Regex::new(r"^4[0-9]{12}(?:[0-9]{3})?$")); + map.insert("Discover", Regex::new(r"^65[4-9][0-9]{13}|64[4-9][0-9]{13}|6011[0-9]{12}|(622(?:12[6-9]|1[3-9][0-9]|[2-8][0-9][0-9]|9[01][0-9]|92[0-5])[0-9]{10})$")); + map.insert( + "Maestro", + Regex::new(r"^(5018|5081|5044|504681|504993|5020|502260|5038|603845|603123|6304|6759|676[1-3]|6220|504834|504817|504645|504775|600206|627741)"), + ); + map.insert( + "RuPay", + Regex::new(r"^(508227|508[5-9]|603741|60698[5-9]|60699|607[0-8]|6079[0-7]|60798[0-4]|60800[1-9]|6080[1-9]|608[1-4]|608500|6521[5-9]|652[2-9]|6530|6531[0-4]|817290|817368|817378|353800)"), + ); + map.insert("Diners Club", Regex::new(r"^(36|38|30[0-5])")); + map.insert( + "JCB", + Regex::new(r"^(3(?:088|096|112|158|337|5(?:2[89]|[3-8][0-9]))\d{12})$"), + ); + map.insert("CarteBlanche", Regex::new(r"^389[0-9]{11}$")); + map.insert("Sodex", Regex::new(r"^(637513)")); + map.insert("BAJAJ", Regex::new(r"^(203040)")); + map + }, + ); + let mut no_of_supported_card_networks = 0; + + let card_number_str = self.get_card_no(); + for (_, regex) in CARD_NETWORK_REGEX.iter() { + let card_regex = match regex.as_ref() { + Ok(regex) => Ok(regex), + Err(_) => Err(report!(ValidationError::InvalidValue { + message: "Invalid regex expression".into(), + })), + }?; + + if card_regex.is_match(&card_number_str) { + no_of_supported_card_networks += 1; + if no_of_supported_card_networks > 1 { + break; + } + } + } + Ok(no_of_supported_card_networks > 1) + } } impl FromStr for CardNumber { diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index dfb0963c70a0..50ce6aea0f4c 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -1112,6 +1112,38 @@ pub enum CardBrand { Visa, MC, Amex, + Argencard, + Bcmc, + Bijcard, + Cabal, + Cartebancaire, + Codensa, + Cup, + Dankort, + Diners, + Discover, + Electron, + Elo, + Forbrugsforeningen, + Hiper, + Hipercard, + Jcb, + Karenmillen, + Laser, + Maestro, + Maestrouk, + Mcalphabankbonus, + Mir, + Naranja, + Oasis, + Rupay, + Shopping, + Solo, + Troy, + Uatp, + Visaalphabankbonus, + Visadankort, + Warehouse, } #[derive(Default, Debug, Serialize, Deserialize)] @@ -1931,6 +1963,22 @@ impl<'a> TryFrom<&domain::GiftCardData> for AdyenPaymentMethod<'a> { } } +fn get_adyen_card_network(card_network: common_enums::CardNetwork) -> Option { + match card_network { + common_enums::CardNetwork::Visa => Some(CardBrand::Visa), + common_enums::CardNetwork::Mastercard => Some(CardBrand::MC), + common_enums::CardNetwork::CartesBancaires => Some(CardBrand::Cartebancaire), + common_enums::CardNetwork::AmericanExpress => Some(CardBrand::Amex), + common_enums::CardNetwork::JCB => Some(CardBrand::Jcb), + common_enums::CardNetwork::DinersClub => Some(CardBrand::Diners), + common_enums::CardNetwork::Discover => Some(CardBrand::Discover), + common_enums::CardNetwork::UnionPay => Some(CardBrand::Cup), + common_enums::CardNetwork::RuPay => Some(CardBrand::Rupay), + common_enums::CardNetwork::Maestro => Some(CardBrand::Maestro), + common_enums::CardNetwork::Interac => None, + } +} + impl<'a> TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod<'a> { type Error = Error; fn try_from( @@ -1943,7 +1991,7 @@ impl<'a> TryFrom<(&domain::Card, Option>)> for AdyenPaymentMethod expiry_year: card.get_expiry_year_4_digit(), cvc: Some(card.card_cvc.clone()), holder_name: card_holder_name, - brand: None, + brand: card.card_network.clone().and_then(get_adyen_card_network), network_payment_reference: None, }; Ok(AdyenPaymentMethod::AdyenCard(Box::new(adyen_card))) @@ -1998,7 +2046,11 @@ impl TryFrom<&utils::CardIssuer> for CardBrand { utils::CardIssuer::AmericanExpress => Ok(Self::Amex), utils::CardIssuer::Master => Ok(Self::MC), utils::CardIssuer::Visa => Ok(Self::Visa), - _ => Err(errors::ConnectorError::NotImplemented("CardBrand".to_string()).into()), + utils::CardIssuer::Maestro => Ok(Self::Maestro), + utils::CardIssuer::Discover => Ok(Self::Discover), + utils::CardIssuer::DinersClub => Ok(Self::Diners), + utils::CardIssuer::JCB => Ok(Self::Jcb), + utils::CardIssuer::CarteBlanche => Ok(Self::Cartebancaire), } } } @@ -2529,8 +2581,12 @@ impl<'a> payments::MandateReferenceId::NetworkMandateId(network_mandate_id) => { match item.router_data.request.payment_method_data { domain::PaymentMethodData::Card(ref card) => { - let card_issuer = card.get_card_issuer()?; - let brand = CardBrand::try_from(&card_issuer)?; + let brand = match card.card_network.clone().and_then(get_adyen_card_network) + { + Some(card_network) => card_network, + None => CardBrand::try_from(&card.get_card_issuer()?)?, + }; + let card_holder_name = item.router_data.get_optional_billing_full_name(); let adyen_card = AdyenCard { payment_type: PaymentType::Scheme, diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index f47ab4139e8b..b987a6717a16 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -535,6 +535,22 @@ impl From for String { } } +fn get_boa_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { + match card_network { + common_enums::CardNetwork::Visa => Some("001"), + common_enums::CardNetwork::Mastercard => Some("002"), + common_enums::CardNetwork::AmericanExpress => Some("003"), + common_enums::CardNetwork::JCB => Some("007"), + common_enums::CardNetwork::DinersClub => Some("005"), + common_enums::CardNetwork::Discover => Some("004"), + common_enums::CardNetwork::CartesBancaires => Some("006"), + common_enums::CardNetwork::UnionPay => Some("062"), + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + common_enums::CardNetwork::Maestro => Some("042"), + common_enums::CardNetwork::Interac | common_enums::CardNetwork::RuPay => None, + } +} + #[derive(Debug, Serialize)] pub enum PaymentSolution { ApplePay, @@ -2418,10 +2434,9 @@ impl TryFrom<&domain::Card> for PaymentInformation { type Error = error_stack::Report; fn try_from(ccard: &domain::Card) -> Result { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard.card_network.clone().and_then(get_boa_card_type) { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; Ok(Self::Cards(Box::new(CardPaymentInformation { card: Card { diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index 864f77b55ddc..38bb016ff258 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -126,10 +126,13 @@ impl TryFrom<&types::SetupMandateRouterData> for CybersourceZeroMandateRequest { let (payment_information, solution) = match item.request.payment_method_data.clone() { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; ( PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -1161,10 +1164,13 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let security_code = if item @@ -1336,10 +1342,13 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, bill_to)); - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -1841,10 +1850,13 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsAuthorizeRouterData>> ) -> Result { match item.router_data.request.payment_method_data.clone() { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; let payment_information = PaymentInformation::Cards(Box::new(CardPaymentInformation { @@ -2565,10 +2577,13 @@ impl TryFrom<&CybersourceRouterData<&types::PaymentsPreProcessingRouterData>> )?; let payment_information = match payment_method_data { domain::PaymentMethodData::Card(ccard) => { - let card_issuer = ccard.get_card_issuer(); - let card_type = match card_issuer { - Ok(issuer) => Some(String::from(issuer)), - Err(_) => None, + let card_type = match ccard + .card_network + .clone() + .and_then(get_cybersource_card_type) + { + Some(card_network) => Some(card_network.to_string()), + None => ccard.get_card_issuer().ok().map(String::from), }; Ok(PaymentInformation::Cards(Box::new( CardPaymentInformation { @@ -3823,3 +3838,19 @@ pub fn get_error_reason( (None, None, None) => None, } } + +fn get_cybersource_card_type(card_network: common_enums::CardNetwork) -> Option<&'static str> { + match card_network { + common_enums::CardNetwork::Visa => Some("001"), + common_enums::CardNetwork::Mastercard => Some("002"), + common_enums::CardNetwork::AmericanExpress => Some("003"), + common_enums::CardNetwork::JCB => Some("007"), + common_enums::CardNetwork::DinersClub => Some("005"), + common_enums::CardNetwork::Discover => Some("004"), + common_enums::CardNetwork::CartesBancaires => Some("006"), + common_enums::CardNetwork::UnionPay => Some("062"), + //"042" is the type code for Masetro Cards(International). For Maestro Cards(UK-Domestic) the mapping should be "024" + common_enums::CardNetwork::Maestro => Some("042"), + common_enums::CardNetwork::Interac | common_enums::CardNetwork::RuPay => None, + } +} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index a81fecba9cf7..4f76acc00287 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -88,6 +88,14 @@ pub enum Auth3ds { Any, } +#[derive(Debug, Eq, PartialEq, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum StripeCardNetwork { + CartesBancaires, + Mastercard, + Visa, +} + #[derive(Debug, Eq, PartialEq, Serialize)] #[serde( rename_all = "snake_case", @@ -220,6 +228,8 @@ pub struct StripeCardData { pub payment_method_data_card_cvc: Option>, #[serde(rename = "payment_method_options[card][request_three_d_secure]")] pub payment_method_auth_type: Option, + #[serde(rename = "payment_method_options[card][network]")] + pub payment_method_data_card_preferred_network: Option, } #[derive(Debug, Eq, PartialEq, Serialize)] pub struct StripePayLaterData { @@ -1344,6 +1354,22 @@ fn create_stripe_payment_method( } } +fn get_stripe_card_network(card_network: common_enums::CardNetwork) -> Option { + match card_network { + common_enums::CardNetwork::Visa => Some(StripeCardNetwork::Visa), + common_enums::CardNetwork::Mastercard => Some(StripeCardNetwork::Mastercard), + common_enums::CardNetwork::CartesBancaires => Some(StripeCardNetwork::CartesBancaires), + common_enums::CardNetwork::AmericanExpress + | common_enums::CardNetwork::JCB + | common_enums::CardNetwork::DinersClub + | common_enums::CardNetwork::Discover + | common_enums::CardNetwork::UnionPay + | common_enums::CardNetwork::Interac + | common_enums::CardNetwork::RuPay + | common_enums::CardNetwork::Maestro => None, + } +} + impl TryFrom<(&domain::Card, Auth3ds)> for StripePaymentMethodData { type Error = errors::ConnectorError; fn try_from( @@ -1356,6 +1382,10 @@ impl TryFrom<(&domain::Card, Auth3ds)> for StripePaymentMethodData { payment_method_data_card_exp_year: card.card_exp_year.clone(), payment_method_data_card_cvc: Some(card.card_cvc.clone()), payment_method_auth_type: Some(payment_method_auth_type), + payment_method_data_card_preferred_network: card + .card_network + .clone() + .and_then(get_stripe_card_network), })) } } @@ -1699,6 +1729,10 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent payment_method_data_card_exp_year: card.card_exp_year.clone(), payment_method_data_card_cvc: None, payment_method_auth_type: None, + payment_method_data_card_preferred_network: card + .card_network + .clone() + .and_then(get_stripe_card_network), }) } domain::payments::PaymentMethodData::CardRedirect(_) diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 9e70cb2b96b0..1b93f05d823a 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -2,6 +2,7 @@ pub mod opensearch; #[cfg(feature = "olap")] pub mod user; pub mod user_role; + use common_utils::consts; pub use hyperswitch_interfaces::consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}; // ID generation diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 3782026835b7..aac6f961cb2d 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4151,7 +4151,10 @@ pub async fn get_additional_payment_data( pm_data: &domain::PaymentMethodData, db: &dyn StorageInterface, profile_id: &id_type::ProfileId, -) -> Option { +) -> Result< + Option, + error_stack::Report, +> { match pm_data { domain::PaymentMethodData::Card(card_data) => { //todo! @@ -4168,17 +4171,29 @@ pub async fn get_additional_payment_data( } _ => None, }; + + let card_network = match card_data + .card_number + .is_cobadged_card() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Card cobadge check failed due to an invalid card network regex", + )? { + true => card_data.card_network.clone(), + false => None, + }; + let last4 = Some(card_data.card_number.get_last4()); if card_data.card_issuer.is_some() - && card_data.card_network.is_some() + && card_network.is_some() && card_data.card_type.is_some() && card_data.card_issuing_country.is_some() && card_data.bank_code.is_some() { - Some(api_models::payments::AdditionalPaymentData::Card(Box::new( - api_models::payments::AdditionalCardInfo { + Ok(Some(api_models::payments::AdditionalPaymentData::Card( + Box::new(api_models::payments::AdditionalCardInfo { card_issuer: card_data.card_issuer.to_owned(), - card_network: card_data.card_network.clone(), + card_network, card_type: card_data.card_type.to_owned(), card_issuing_country: card_data.card_issuing_country.to_owned(), bank_code: card_data.bank_code.to_owned(), @@ -4191,7 +4206,7 @@ pub async fn get_additional_payment_data( // These are filled after calling the processor / connector payment_checks: None, authentication_data: None, - }, + }), ))) } else { let card_info = card_isin @@ -4208,7 +4223,7 @@ pub async fn get_additional_payment_data( api_models::payments::AdditionalPaymentData::Card(Box::new( api_models::payments::AdditionalCardInfo { card_issuer: card_info.card_issuer, - card_network: card_info.card_network.clone(), + card_network, bank_code: card_info.bank_code, card_type: card_info.card_type, card_issuing_country: card_info.card_issuing_country, @@ -4224,7 +4239,7 @@ pub async fn get_additional_payment_data( }, )) }); - Some(card_info.unwrap_or_else(|| { + Ok(Some(card_info.unwrap_or_else(|| { api_models::payments::AdditionalPaymentData::Card(Box::new( api_models::payments::AdditionalCardInfo { card_issuer: None, @@ -4243,42 +4258,44 @@ pub async fn get_additional_payment_data( authentication_data: None, }, )) - })) + }))) } } domain::PaymentMethodData::BankRedirect(bank_redirect_data) => match bank_redirect_data { - domain::BankRedirectData::Eps { bank_name, .. } => { - Some(api_models::payments::AdditionalPaymentData::BankRedirect { + domain::BankRedirectData::Eps { bank_name, .. } => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: bank_name.to_owned(), details: None, - }) - } - domain::BankRedirectData::Ideal { bank_name, .. } => { - Some(api_models::payments::AdditionalPaymentData::BankRedirect { + }, + )), + domain::BankRedirectData::Ideal { bank_name, .. } => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: bank_name.to_owned(), details: None, - }) - } + }, + )), domain::BankRedirectData::BancontactCard { card_number, card_exp_month, card_exp_year, card_holder_name, - } => Some(api_models::payments::AdditionalPaymentData::BankRedirect { - bank_name: None, - details: Some( - payment_additional_types::BankRedirectDetails::BancontactCard(Box::new( - payment_additional_types::BancontactBankRedirectAdditionalData { - last4: card_number.as_ref().map(|c| c.get_last4()), - card_exp_month: card_exp_month.clone(), - card_exp_year: card_exp_year.clone(), - card_holder_name: card_holder_name.clone(), - }, - )), - ), - }), - domain::BankRedirectData::Blik { blik_code } => { - Some(api_models::payments::AdditionalPaymentData::BankRedirect { + } => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: Some( + payment_additional_types::BankRedirectDetails::BancontactCard(Box::new( + payment_additional_types::BancontactBankRedirectAdditionalData { + last4: card_number.as_ref().map(|c| c.get_last4()), + card_exp_month: card_exp_month.clone(), + card_exp_year: card_exp_year.clone(), + card_holder_name: card_holder_name.clone(), + }, + )), + ), + }, + )), + domain::BankRedirectData::Blik { blik_code } => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { bank_name: None, details: blik_code.as_ref().map(|blik_code| { payment_additional_types::BankRedirectDetails::Blik(Box::new( @@ -4287,119 +4304,123 @@ pub async fn get_additional_payment_data( }, )) }), - }) - } + }, + )), domain::BankRedirectData::Giropay { bank_account_bic, bank_account_iban, country, - } => Some(api_models::payments::AdditionalPaymentData::BankRedirect { - bank_name: None, - details: Some(payment_additional_types::BankRedirectDetails::Giropay( - Box::new( - payment_additional_types::GiropayBankRedirectAdditionalData { - bic: bank_account_bic - .as_ref() - .map(|bic| MaskedSortCode::from(bic.to_owned())), - iban: bank_account_iban - .as_ref() - .map(|iban| MaskedIban::from(iban.to_owned())), - country: *country, - }, - ), - )), - }), - _ => Some(api_models::payments::AdditionalPaymentData::BankRedirect { - bank_name: None, - details: None, - }), + } => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: Some(payment_additional_types::BankRedirectDetails::Giropay( + Box::new( + payment_additional_types::GiropayBankRedirectAdditionalData { + bic: bank_account_bic + .as_ref() + .map(|bic| MaskedSortCode::from(bic.to_owned())), + iban: bank_account_iban + .as_ref() + .map(|iban| MaskedIban::from(iban.to_owned())), + country: *country, + }, + ), + )), + }, + )), + _ => Ok(Some( + api_models::payments::AdditionalPaymentData::BankRedirect { + bank_name: None, + details: None, + }, + )), }, domain::PaymentMethodData::Wallet(wallet) => match wallet { domain::WalletData::ApplePay(apple_pay_wallet_data) => { - Some(api_models::payments::AdditionalPaymentData::Wallet { + Ok(Some(api_models::payments::AdditionalPaymentData::Wallet { apple_pay: Some(api_models::payments::ApplepayPaymentMethod { display_name: apple_pay_wallet_data.payment_method.display_name.clone(), network: apple_pay_wallet_data.payment_method.network.clone(), pm_type: apple_pay_wallet_data.payment_method.pm_type.clone(), }), google_pay: None, - }) + })) } domain::WalletData::GooglePay(google_pay_pm_data) => { - Some(api_models::payments::AdditionalPaymentData::Wallet { + Ok(Some(api_models::payments::AdditionalPaymentData::Wallet { apple_pay: None, google_pay: Some(payment_additional_types::WalletAdditionalDataForCard { last4: google_pay_pm_data.info.card_details.clone(), card_network: google_pay_pm_data.info.card_network.clone(), card_type: google_pay_pm_data.pm_type.clone(), }), - }) + })) } - _ => Some(api_models::payments::AdditionalPaymentData::Wallet { + _ => Ok(Some(api_models::payments::AdditionalPaymentData::Wallet { apple_pay: None, google_pay: None, - }), + })), }, - domain::PaymentMethodData::PayLater(_) => { - Some(api_models::payments::AdditionalPaymentData::PayLater { klarna_sdk: None }) - } - domain::PaymentMethodData::BankTransfer(bank_transfer) => { - Some(api_models::payments::AdditionalPaymentData::BankTransfer { + domain::PaymentMethodData::PayLater(_) => Ok(Some( + api_models::payments::AdditionalPaymentData::PayLater { klarna_sdk: None }, + )), + domain::PaymentMethodData::BankTransfer(bank_transfer) => Ok(Some( + api_models::payments::AdditionalPaymentData::BankTransfer { details: Some((*(bank_transfer.to_owned())).into()), - }) - } + }, + )), domain::PaymentMethodData::Crypto(crypto) => { - Some(api_models::payments::AdditionalPaymentData::Crypto { + Ok(Some(api_models::payments::AdditionalPaymentData::Crypto { details: Some(crypto.to_owned().into()), - }) + })) } - domain::PaymentMethodData::BankDebit(bank_debit) => { - Some(api_models::payments::AdditionalPaymentData::BankDebit { + domain::PaymentMethodData::BankDebit(bank_debit) => Ok(Some( + api_models::payments::AdditionalPaymentData::BankDebit { details: Some(bank_debit.to_owned().into()), - }) - } - domain::PaymentMethodData::MandatePayment => { - Some(api_models::payments::AdditionalPaymentData::MandatePayment {}) - } + }, + )), + domain::PaymentMethodData::MandatePayment => Ok(Some( + api_models::payments::AdditionalPaymentData::MandatePayment {}, + )), domain::PaymentMethodData::Reward => { - Some(api_models::payments::AdditionalPaymentData::Reward {}) + Ok(Some(api_models::payments::AdditionalPaymentData::Reward {})) } - domain::PaymentMethodData::RealTimePayment(realtime_payment) => Some( + domain::PaymentMethodData::RealTimePayment(realtime_payment) => Ok(Some( api_models::payments::AdditionalPaymentData::RealTimePayment { details: Some((*(realtime_payment.to_owned())).into()), }, - ), + )), domain::PaymentMethodData::Upi(upi) => { - Some(api_models::payments::AdditionalPaymentData::Upi { + Ok(Some(api_models::payments::AdditionalPaymentData::Upi { details: Some(upi.to_owned().into()), - }) + })) } - domain::PaymentMethodData::CardRedirect(card_redirect) => { - Some(api_models::payments::AdditionalPaymentData::CardRedirect { + domain::PaymentMethodData::CardRedirect(card_redirect) => Ok(Some( + api_models::payments::AdditionalPaymentData::CardRedirect { details: Some(card_redirect.to_owned().into()), - }) - } + }, + )), domain::PaymentMethodData::Voucher(voucher) => { - Some(api_models::payments::AdditionalPaymentData::Voucher { + Ok(Some(api_models::payments::AdditionalPaymentData::Voucher { details: Some(voucher.to_owned().into()), - }) + })) } - domain::PaymentMethodData::GiftCard(gift_card) => { - Some(api_models::payments::AdditionalPaymentData::GiftCard { + domain::PaymentMethodData::GiftCard(gift_card) => Ok(Some( + api_models::payments::AdditionalPaymentData::GiftCard { details: Some((*(gift_card.to_owned())).into()), - }) - } - domain::PaymentMethodData::CardToken(card_token) => { - Some(api_models::payments::AdditionalPaymentData::CardToken { + }, + )), + domain::PaymentMethodData::CardToken(card_token) => Ok(Some( + api_models::payments::AdditionalPaymentData::CardToken { details: Some(card_token.to_owned().into()), - }) - } - domain::PaymentMethodData::OpenBanking(open_banking) => { - Some(api_models::payments::AdditionalPaymentData::OpenBanking { + }, + )), + domain::PaymentMethodData::OpenBanking(open_banking) => Ok(Some( + api_models::payments::AdditionalPaymentData::OpenBanking { details: Some(open_banking.to_owned().into()), - }) - } - domain::PaymentMethodData::NetworkToken(_) => None, + }, + )), + domain::PaymentMethodData::NetworkToken(_) => Ok(None), } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 658a02f7d080..fb8f94405ee0 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -473,7 +473,7 @@ impl GetTracker, api::PaymentsRequest> for Pa let additional_pm_data_fut = tokio::spawn( async move { Ok(n_request_payment_method_data - .async_and_then(|payment_method_data| async move { + .async_map(|payment_method_data| async move { helpers::get_additional_payment_data( &payment_method_data.into(), store.as_ref(), @@ -555,12 +555,14 @@ impl GetTracker, api::PaymentsRequest> for Pa ); // Parallel calls - level 2 - let (mandate_details, additional_pm_data, payment_method_billing) = tokio::try_join!( + let (mandate_details, additional_pm_info, payment_method_billing) = tokio::try_join!( utils::flatten_join_error(mandate_details_fut), utils::flatten_join_error(additional_pm_data_fut), utils::flatten_join_error(payment_method_billing_future), )?; + let additional_pm_data = additional_pm_info.transpose()?.flatten(); + let m_helpers::MandateGenericData { token, payment_method, @@ -640,7 +642,10 @@ impl GetTracker, api::PaymentsRequest> for Pa .zip(additional_pm_data) .map(|(payment_method_data, additional_payment_data)| { payment_method_data.apply_additional_payment_data(additional_payment_data) - }); + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Card cobadge check failed due to an invalid card network regex")?; payment_attempt.payment_method_billing_address_id = payment_method_billing .as_ref() @@ -1163,6 +1168,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .await }) .await + .transpose()? + .flatten(); + + let encoded_additional_pm_data = additional_pm_data .as_ref() .map(Encode::encode_to_value) .transpose() @@ -1251,7 +1260,9 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen let m_connector = connector.clone(); let m_capture_method = capture_method; let m_payment_token = payment_token.clone(); - let m_additional_pm_data = additional_pm_data.clone().or(encode_additional_pm_to_value); + let m_additional_pm_data = encoded_additional_pm_data + .clone() + .or(encode_additional_pm_to_value); let m_business_sub_label = business_sub_label.clone(); let m_straight_through_algorithm = straight_through_algorithm.clone(); let m_error_code = error_code.clone(); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 0921e38317a6..f23405e35514 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -498,7 +498,10 @@ impl GetTracker, api::PaymentsRequest> for Pa .zip(additional_payment_data) .map(|(payment_method_data, additional_payment_data)| { payment_method_data.apply_additional_payment_data(additional_payment_data) - }); + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Card cobadge check failed due to an invalid card network regex")?; let amount = payment_attempt.get_total_amount().into(); @@ -1058,7 +1061,7 @@ impl PaymentCreate { .and_then(|payment_method_data_request| { payment_method_data_request.payment_method_data.clone() }) - .async_and_then(|payment_method_data| async { + .async_map(|payment_method_data| async { helpers::get_additional_payment_data( &payment_method_data.into(), &*state.store, @@ -1066,7 +1069,9 @@ impl PaymentCreate { ) .await }) - .await; + .await + .transpose()? + .flatten(); if additional_pm_data.is_none() { // If recurring payment is made using payment_method_id, then fetch payment_method_data from retrieved payment_method object diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index c69595d1893e..3fa44868f0d3 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -749,6 +749,10 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen .await }) .await + .transpose()? + .flatten(); + + let encoded_pm_data = additional_pm_data .as_ref() .map(Encode::encode_to_value) .transpose() @@ -785,7 +789,7 @@ impl UpdateTracker, api::PaymentsRequest> for Paymen authentication_type: None, payment_method, payment_token: payment_data.token.clone(), - payment_method_data: additional_pm_data, + payment_method_data: encoded_pm_data, payment_experience, payment_method_type, business_sub_label,