Skip to content

feat(router): add debit routing support for saved card flow #7923

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: debit-routing/add-core-logic
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 26 additions & 3 deletions crates/api_models/src/open_router.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use std::{collections::HashMap, fmt::Debug};

use crate::enums::{Currency, PaymentMethod};
use crate::payment_methods;
use common_utils::{id_type, types::MinorUnit};
pub use euclid::{
dssa::types::EuclidAnalysable,
Expand All @@ -10,8 +12,6 @@ pub use euclid::{
};
use serde::{Deserialize, Serialize};

use crate::enums::{Currency, PaymentMethod};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenRouterDecideGatewayRequest {
Expand Down Expand Up @@ -49,7 +49,6 @@ pub struct PaymentInfo {
// paymentSource: Option<String>,
// authType: Option<ETCa::txn_card_info::AuthType>,
// cardIssuerBankName: Option<String>,

pub card_isin: Option<String>,
// cardType: Option<ETCa::card_type::CardType>,
// cardSwitchProvider: Option<Secret<String>>,
Expand All @@ -70,6 +69,30 @@ pub struct DebitRoutingOutput {
pub card_type: common_enums::CardType,
}

impl From<&DebitRoutingOutput> for payment_methods::CoBadgedCardData {
fn from(output: &DebitRoutingOutput) -> Self {
Self {
co_badged_card_networks: output.co_badged_card_networks.clone(),
issuer_country: output.issuer_country,
is_regulated: output.is_regulated,
regulated_name: output.regulated_name,
card_type: output.card_type,
}
}
}

impl From<payment_methods::CoBadgedCardData> for DebitRoutingRequestData {
fn from(output: payment_methods::CoBadgedCardData) -> Self {
Self {
co_badged_card_networks: output.co_badged_card_networks,
issuer_country: output.issuer_country,
is_regulated: output.is_regulated,
regulated_name: output.regulated_name,
card_type: output.card_type,
}
}
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CoBadgedCardRequest {
pub merchant_category_code: common_enums::MerchantCategoryCode,
Expand Down
19 changes: 17 additions & 2 deletions crates/api_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -987,6 +987,16 @@ pub struct CardDetailsPaymentMethod {
pub card_type: Option<String>,
#[serde(default = "saved_in_locker_default")]
pub saved_to_locker: bool,
pub co_badged_card_data: Option<CoBadgedCardData>,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct CoBadgedCardData {
pub co_badged_card_networks: Vec<api_enums::CardNetwork>,
pub issuer_country: common_enums::CountryAlpha2,
pub is_regulated: bool,
pub regulated_name: Option<common_enums::RegulatedName>,
pub card_type: common_enums::CardType,
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)]
Expand Down Expand Up @@ -1314,6 +1324,7 @@ impl From<CardDetail> for CardDetailsPaymentMethod {
card_network: item.card_network,
card_type: item.card_type.map(|card| card.to_string()),
saved_to_locker: true,
co_badged_card_data: None,
}
}
}
Expand All @@ -1322,8 +1333,10 @@ impl From<CardDetail> for CardDetailsPaymentMethod {
any(feature = "v1", feature = "v2"),
not(feature = "payment_methods_v2")
))]
impl From<CardDetailFromLocker> for CardDetailsPaymentMethod {
fn from(item: CardDetailFromLocker) -> Self {
impl From<(CardDetailFromLocker, Option<&CoBadgedCardData>)> for CardDetailsPaymentMethod {
fn from(
(item, co_badged_card_data): (CardDetailFromLocker, Option<&CoBadgedCardData>),
) -> Self {
Self {
issuer_country: item.issuer_country,
last4_digits: item.last4_digits,
Expand All @@ -1336,6 +1349,7 @@ impl From<CardDetailFromLocker> for CardDetailsPaymentMethod {
card_network: item.card_network,
card_type: item.card_type,
saved_to_locker: item.saved_to_locker,
co_badged_card_data: co_badged_card_data.cloned(),
}
}
}
Expand All @@ -1355,6 +1369,7 @@ impl From<CardDetailFromLocker> for CardDetailsPaymentMethod {
card_network: item.card_network,
card_type: item.card_type,
saved_to_locker: item.saved_to_locker,
co_badged_card_data: None,
}
}
}
Expand Down
1 change: 1 addition & 0 deletions crates/common_enums/src/enums.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2272,6 +2272,7 @@ pub enum MandateStatus {
/// Indicates the card network.
#[derive(
Clone,
Copy,
Debug,
Eq,
Hash,
Expand Down
51 changes: 47 additions & 4 deletions crates/hyperswitch_domain_models/src/payment_method_data.rs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ impl PaymentMethodData {
pub fn is_network_token_payment_method_data(&self) -> bool {
matches!(self, Self::NetworkToken(_))
}

pub fn get_co_badged_card_data(&self) -> Option<&payment_methods::CoBadgedCardData> {
if let Self::Card(card) = self {
card.co_badged_card_data.as_ref()
} else {
None
}
}
}

#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
Expand All @@ -87,11 +95,13 @@ pub struct Card {
pub card_cvc: Secret<String>,
pub card_issuer: Option<String>,
pub card_network: Option<common_enums::CardNetwork>,
// we use this as the card type for co-badged cards as well
pub card_type: Option<String>,
pub card_issuing_country: Option<String>,
pub bank_code: Option<String>,
pub nick_name: Option<Secret<String>>,
pub card_holder_name: Option<Secret<String>>,
pub co_badged_card_data: Option<payment_methods::CoBadgedCardData>,
}

#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)]
Expand Down Expand Up @@ -120,6 +130,7 @@ pub struct CardDetail {
pub bank_code: Option<String>,
pub nick_name: Option<Secret<String>>,
pub card_holder_name: Option<Secret<String>>,
pub co_badged_card_data: Option<payment_methods::CoBadgedCardData>,
}

impl CardDetailsForNetworkTransactionId {
Expand Down Expand Up @@ -162,6 +173,7 @@ impl From<&Card> for CardDetail {
bank_code: item.bank_code.to_owned(),
nick_name: item.nick_name.to_owned(),
card_holder_name: item.card_holder_name.to_owned(),
co_badged_card_data: item.co_badged_card_data.to_owned(),
}
}
}
Expand Down Expand Up @@ -723,6 +735,7 @@ impl TryFrom<payment_methods::PaymentMethodCreateData> for PaymentMethodData {
bank_code: None,
nick_name,
card_holder_name,
co_badged_card_data: None,
})),
}
}
Expand All @@ -732,7 +745,7 @@ impl From<api_models::payments::PaymentMethodData> for PaymentMethodData {
fn from(api_model_payment_method_data: api_models::payments::PaymentMethodData) -> Self {
match api_model_payment_method_data {
api_models::payments::PaymentMethodData::Card(card_data) => {
Self::Card(Card::from(card_data))
Self::Card(Card::from((card_data, None)))
}
api_models::payments::PaymentMethodData::CardRedirect(card_redirect) => {
Self::CardRedirect(From::from(card_redirect))
Expand Down Expand Up @@ -782,8 +795,25 @@ impl From<api_models::payments::PaymentMethodData> for PaymentMethodData {
}
}

impl From<api_models::payments::Card> for Card {
fn from(value: api_models::payments::Card) -> Self {
impl
From<(
api_models::payments::Card,
Option<payment_methods::CoBadgedCardData>,
)> for Card
{
fn from(
(value, co_badged_card_data_optional): (
api_models::payments::Card,
Option<payment_methods::CoBadgedCardData>,
),
) -> Self {
let first_co_badged_card_network =
co_badged_card_data_optional
.as_ref()
.and_then(|co_badged_card_data| {
co_badged_card_data.co_badged_card_networks.first().cloned()
});

let api_models::payments::Card {
card_number,
card_exp_month,
Expand All @@ -804,12 +834,13 @@ impl From<api_models::payments::Card> for Card {
card_exp_year,
card_cvc,
card_issuer,
card_network,
card_network: first_co_badged_card_network.or(card_network),
card_type,
card_issuing_country,
bank_code,
nick_name,
card_holder_name,
co_badged_card_data: co_badged_card_data_optional,
}
}
}
Expand Down Expand Up @@ -1875,6 +1906,16 @@ pub enum PaymentMethodsData {
NetworkToken(NetworkTokenDetailsPaymentMethod),
}

impl PaymentMethodsData {
pub fn get_co_badged_card_data(&self) -> Option<payment_methods::CoBadgedCardData> {
if let Self::Card(card) = self {
card.co_badged_card_data.clone()
} else {
None
}
}
}

#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)]
pub struct NetworkTokenDetailsPaymentMethod {
pub last4_digits: Option<String>,
Expand Down Expand Up @@ -1909,6 +1950,7 @@ pub struct CardDetailsPaymentMethod {
pub card_type: Option<String>,
#[serde(default = "saved_in_locker_default")]
pub saved_to_locker: bool,
pub co_badged_card_data: Option<payment_methods::CoBadgedCardData>,
}

#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
Expand All @@ -1926,6 +1968,7 @@ impl From<payment_methods::CardDetail> for CardDetailsPaymentMethod {
card_network: item.card_network,
card_type: item.card_type.map(|card| card.to_string()),
saved_to_locker: true,
co_badged_card_data: None,
}
}
}
Expand Down
30 changes: 26 additions & 4 deletions crates/hyperswitch_domain_models/src/payment_methods.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#[cfg(feature = "v2")]
use api_models::payment_methods::PaymentMethodsData;

use crate::payment_method_data as domain_payment_method_data;
#[cfg(feature = "v1")]
use masking::ExposeInterface;
#[cfg(feature = "v1")]
use router_env::logger;
// specific imports because of using the macro
use common_enums::enums::MerchantStorageScheme;
#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
Expand All @@ -20,10 +26,7 @@ use serde_json::Value;
use time::PrimitiveDateTime;

#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
use crate::{
address::Address, payment_method_data as domain_payment_method_data,
type_encryption::OptionalEncryptableJsonType,
};
use crate::{address::Address, type_encryption::OptionalEncryptableJsonType};
use crate::{
mandates::{self, CommonMandateReference},
merchant_key_store::MerchantKeyStore,
Expand Down Expand Up @@ -131,6 +134,25 @@ impl PaymentMethod {
&self.payment_method_id
}

#[cfg(feature = "v1")]
pub fn get_payment_methods_data(
&self,
) -> Option<domain_payment_method_data::PaymentMethodsData> {
self.payment_method_data
.clone()
.map(|value| value.into_inner().expose())
.and_then(|value| {
serde_json::from_value::<domain_payment_method_data::PaymentMethodsData>(value)
.map_err(|error| {
logger::warn!(
?error,
"Failed to parse payment method data in payment method info"
);
})
.ok()
})
}

#[cfg(all(feature = "v2", feature = "payment_methods_v2"))]
pub fn get_id(&self) -> &id_type::GlobalPaymentMethodId {
&self.id
Expand Down
Loading
Loading