mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(router): add support for co-badged cards (#5801)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -1846,6 +1846,7 @@ dependencies = [
|
||||
"common_utils",
|
||||
"error-stack",
|
||||
"masking",
|
||||
"regex",
|
||||
"router_env",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
||||
@ -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,23 @@ 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<Self, error_stack::Report<ValidationError>> {
|
||||
let card_network = self
|
||||
.card_network
|
||||
.clone()
|
||||
.or(additional_card_info.card_network.clone())
|
||||
.map(|network| match self.card_number.is_cobadged_card() {
|
||||
Ok(true) => Ok(Some(network)),
|
||||
Ok(false) => Ok(None),
|
||||
Err(e) => Err(e),
|
||||
})
|
||||
.transpose()?
|
||||
.flatten();
|
||||
|
||||
Ok(Self {
|
||||
card_number: self.card_number.clone(),
|
||||
card_exp_month: self.card_exp_month.clone(),
|
||||
card_exp_year: self.card_exp_year.clone(),
|
||||
@ -1407,10 +1423,7 @@ impl Card {
|
||||
.card_issuer
|
||||
.clone()
|
||||
.or(additional_card_info.card_issuer),
|
||||
card_network: self
|
||||
.card_network
|
||||
.clone()
|
||||
.or(additional_card_info.card_network),
|
||||
card_network,
|
||||
card_type: self.card_type.clone().or(additional_card_info.card_type),
|
||||
card_issuing_country: self
|
||||
.card_issuing_country
|
||||
@ -1418,7 +1431,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 +1871,16 @@ impl PaymentMethodData {
|
||||
pub fn apply_additional_payment_data(
|
||||
&self,
|
||||
additional_payment_data: AdditionalPaymentData,
|
||||
) -> Self {
|
||||
) -> Result<Self, error_stack::Report<ValidationError>> {
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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" }
|
||||
|
||||
@ -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::<String>()
|
||||
}
|
||||
pub fn is_cobadged_card(&self) -> Result<bool, error_stack::Report<ValidationError>> {
|
||||
/// Regex to identify card networks
|
||||
static CARD_NETWORK_REGEX: Lazy<HashMap<&str, Result<Regex, regex::Error>>> = 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 {
|
||||
|
||||
@ -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<CardBrand> {
|
||||
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<Secret<String>>)> for AdyenPaymentMethod<'a> {
|
||||
type Error = Error;
|
||||
fn try_from(
|
||||
@ -1943,7 +1991,7 @@ impl<'a> TryFrom<(&domain::Card, Option<Secret<String>>)> 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,
|
||||
|
||||
@ -535,6 +535,22 @@ impl From<CardIssuer> 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<errors::ConnectorError>;
|
||||
|
||||
fn try_from(ccard: &domain::Card) -> Result<Self, Self::Error> {
|
||||
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 {
|
||||
|
||||
@ -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<Self, Self::Error> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<Secret<String>>,
|
||||
#[serde(rename = "payment_method_options[card][request_three_d_secure]")]
|
||||
pub payment_method_auth_type: Option<Auth3ds>,
|
||||
#[serde(rename = "payment_method_options[card][network]")]
|
||||
pub payment_method_data_card_preferred_network: Option<StripeCardNetwork>,
|
||||
}
|
||||
#[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<StripeCardNetwork> {
|
||||
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(_)
|
||||
|
||||
@ -4150,7 +4150,10 @@ pub async fn get_additional_payment_data(
|
||||
pm_data: &domain::PaymentMethodData,
|
||||
db: &dyn StorageInterface,
|
||||
profile_id: &id_type::ProfileId,
|
||||
) -> Option<api_models::payments::AdditionalPaymentData> {
|
||||
) -> Result<
|
||||
Option<api_models::payments::AdditionalPaymentData>,
|
||||
error_stack::Report<errors::ApiErrorResponse>,
|
||||
> {
|
||||
match pm_data {
|
||||
domain::PaymentMethodData::Card(card_data) => {
|
||||
//todo!
|
||||
@ -4167,17 +4170,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(),
|
||||
@ -4190,7 +4205,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
|
||||
@ -4207,7 +4222,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,
|
||||
@ -4223,7 +4238,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,
|
||||
@ -4242,42 +4257,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(
|
||||
@ -4286,119 +4303,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),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -473,7 +473,7 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Send + Clone> GetTracker<F, PaymentData<F>, 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<F: Clone> UpdateTracker<F, PaymentData<F>, 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<F: Clone> UpdateTracker<F, PaymentData<F>, 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();
|
||||
|
||||
@ -491,7 +491,10 @@ impl<F: Send + Clone> GetTracker<F, PaymentData<F>, 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();
|
||||
|
||||
@ -1051,7 +1054,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,
|
||||
@ -1059,7 +1062,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
|
||||
|
||||
@ -749,6 +749,10 @@ impl<F: Clone> UpdateTracker<F, PaymentData<F>, 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<F: Clone> UpdateTracker<F, PaymentData<F>, 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,
|
||||
|
||||
Reference in New Issue
Block a user