mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(debit_routing): add debit routing support for apple pay (#8673)
This commit is contained in:
@ -6,7 +6,7 @@ use common_utils::{
|
||||
errors::CustomResult, ext_traits::ValueExt, id_type, types::keymanager::KeyManagerState,
|
||||
};
|
||||
use error_stack::ResultExt;
|
||||
use masking::Secret;
|
||||
use masking::{PeekInterface, Secret};
|
||||
|
||||
use super::{
|
||||
payments::{OperationSessionGetters, OperationSessionSetters},
|
||||
@ -157,33 +157,76 @@ where
|
||||
match format!("{operation:?}").as_str() {
|
||||
"PaymentConfirm" => {
|
||||
logger::info!("Checking if debit routing is required");
|
||||
let payment_intent = payment_data.get_payment_intent();
|
||||
let payment_attempt = payment_data.get_payment_attempt();
|
||||
|
||||
request_validation(payment_intent, payment_attempt, debit_routing_config)
|
||||
request_validation(payment_data, debit_routing_config)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request_validation(
|
||||
payment_intent: &hyperswitch_domain_models::payments::PaymentIntent,
|
||||
payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt,
|
||||
fn request_validation<F: Clone, D>(
|
||||
payment_data: &D,
|
||||
debit_routing_config: &settings::DebitRoutingConfig,
|
||||
) -> bool {
|
||||
logger::debug!("Validating request for debit routing");
|
||||
let is_currency_supported = payment_intent.currency.map(|currency| {
|
||||
debit_routing_config
|
||||
.supported_currencies
|
||||
.contains(¤cy)
|
||||
});
|
||||
) -> bool
|
||||
where
|
||||
D: OperationSessionGetters<F> + Send + Sync + Clone,
|
||||
{
|
||||
let payment_intent = payment_data.get_payment_intent();
|
||||
let payment_attempt = payment_data.get_payment_attempt();
|
||||
|
||||
let is_currency_supported = is_currency_supported(payment_intent, debit_routing_config);
|
||||
|
||||
let is_valid_payment_method = validate_payment_method_for_debit_routing(payment_data);
|
||||
|
||||
payment_intent.setup_future_usage != Some(enums::FutureUsage::OffSession)
|
||||
&& payment_intent.amount.is_greater_than(0)
|
||||
&& is_currency_supported == Some(true)
|
||||
&& payment_attempt.authentication_type != Some(enums::AuthenticationType::ThreeDs)
|
||||
&& payment_attempt.payment_method == Some(enums::PaymentMethod::Card)
|
||||
&& payment_attempt.payment_method_type == Some(enums::PaymentMethodType::Debit)
|
||||
&& is_currency_supported
|
||||
&& payment_attempt.authentication_type == Some(enums::AuthenticationType::NoThreeDs)
|
||||
&& is_valid_payment_method
|
||||
}
|
||||
|
||||
fn is_currency_supported(
|
||||
payment_intent: &hyperswitch_domain_models::payments::PaymentIntent,
|
||||
debit_routing_config: &settings::DebitRoutingConfig,
|
||||
) -> bool {
|
||||
payment_intent
|
||||
.currency
|
||||
.map(|currency| {
|
||||
debit_routing_config
|
||||
.supported_currencies
|
||||
.contains(¤cy)
|
||||
})
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn validate_payment_method_for_debit_routing<F: Clone, D>(payment_data: &D) -> bool
|
||||
where
|
||||
D: OperationSessionGetters<F> + Send + Sync + Clone,
|
||||
{
|
||||
let payment_attempt = payment_data.get_payment_attempt();
|
||||
match payment_attempt.payment_method {
|
||||
Some(enums::PaymentMethod::Card) => {
|
||||
payment_attempt.payment_method_type == Some(enums::PaymentMethodType::Debit)
|
||||
}
|
||||
Some(enums::PaymentMethod::Wallet) => {
|
||||
payment_attempt.payment_method_type == Some(enums::PaymentMethodType::ApplePay)
|
||||
&& payment_data
|
||||
.get_payment_method_data()
|
||||
.and_then(|data| data.get_wallet_data())
|
||||
.and_then(|data| data.get_apple_pay_wallet_data())
|
||||
.and_then(|data| data.get_payment_method_type())
|
||||
== Some(enums::PaymentMethodType::Debit)
|
||||
&& matches!(
|
||||
payment_data.get_payment_method_token().cloned(),
|
||||
Some(
|
||||
hyperswitch_domain_models::router_data::PaymentMethodToken::ApplePayDecrypt(
|
||||
_
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn check_for_debit_routing_connector_in_profile<
|
||||
@ -318,8 +361,11 @@ pub async fn get_debit_routing_output<
|
||||
) -> Option<open_router::DebitRoutingOutput> {
|
||||
logger::debug!("Fetching sorted card networks");
|
||||
|
||||
let (saved_co_badged_card_data, saved_card_type, card_isin) =
|
||||
extract_saved_card_info(payment_data);
|
||||
let card_info = extract_card_info(payment_data);
|
||||
|
||||
let saved_co_badged_card_data = card_info.co_badged_card_data;
|
||||
let saved_card_type = card_info.card_type;
|
||||
let card_isin = card_info.card_isin;
|
||||
|
||||
match (
|
||||
saved_co_badged_card_data
|
||||
@ -370,46 +416,131 @@ pub async fn get_debit_routing_output<
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_saved_card_info<F, D>(
|
||||
payment_data: &D,
|
||||
) -> (
|
||||
Option<api_models::payment_methods::CoBadgedCardData>,
|
||||
Option<String>,
|
||||
Option<Secret<String>>,
|
||||
)
|
||||
#[derive(Debug, Clone)]
|
||||
struct ExtractedCardInfo {
|
||||
co_badged_card_data: Option<api_models::payment_methods::CoBadgedCardData>,
|
||||
card_type: Option<String>,
|
||||
card_isin: Option<Secret<String>>,
|
||||
}
|
||||
|
||||
impl ExtractedCardInfo {
|
||||
fn new(
|
||||
co_badged_card_data: Option<api_models::payment_methods::CoBadgedCardData>,
|
||||
card_type: Option<String>,
|
||||
card_isin: Option<Secret<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
co_badged_card_data,
|
||||
card_type,
|
||||
card_isin,
|
||||
}
|
||||
}
|
||||
|
||||
fn empty() -> Self {
|
||||
Self::new(None, None, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_card_info<F, D>(payment_data: &D) -> ExtractedCardInfo
|
||||
where
|
||||
D: OperationSessionGetters<F>,
|
||||
{
|
||||
let payment_method_data_optional = payment_data.get_payment_method_data();
|
||||
match payment_data
|
||||
.get_payment_method_info()
|
||||
.and_then(|info| info.get_payment_methods_data())
|
||||
extract_from_saved_payment_method(payment_data)
|
||||
.unwrap_or_else(|| extract_from_payment_method_data(payment_data))
|
||||
}
|
||||
|
||||
fn extract_from_saved_payment_method<F, D>(payment_data: &D) -> Option<ExtractedCardInfo>
|
||||
where
|
||||
D: OperationSessionGetters<F>,
|
||||
{
|
||||
let payment_methods_data = payment_data
|
||||
.get_payment_method_info()?
|
||||
.get_payment_methods_data()?;
|
||||
|
||||
if let hyperswitch_domain_models::payment_method_data::PaymentMethodsData::Card(card) =
|
||||
payment_methods_data
|
||||
{
|
||||
Some(hyperswitch_domain_models::payment_method_data::PaymentMethodsData::Card(card)) => {
|
||||
match (&card.co_badged_card_data, &card.card_isin) {
|
||||
(Some(co_badged), _) => {
|
||||
logger::debug!("Co-badged card data found in saved payment method");
|
||||
(Some(co_badged.clone()), card.card_type, None)
|
||||
}
|
||||
(None, Some(card_isin)) => {
|
||||
logger::debug!("No co-badged data; using saved card ISIN");
|
||||
(None, None, Some(Secret::new(card_isin.clone())))
|
||||
}
|
||||
_ => (None, None, None),
|
||||
}
|
||||
}
|
||||
_ => match payment_method_data_optional {
|
||||
Some(hyperswitch_domain_models::payment_method_data::PaymentMethodData::Card(card)) => {
|
||||
logger::debug!("Using card data from payment request");
|
||||
(
|
||||
None,
|
||||
None,
|
||||
Some(Secret::new(card.card_number.get_card_isin())),
|
||||
)
|
||||
}
|
||||
_ => (None, None, None),
|
||||
},
|
||||
return Some(extract_card_info_from_saved_card(&card));
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn extract_card_info_from_saved_card(
|
||||
card: &hyperswitch_domain_models::payment_method_data::CardDetailsPaymentMethod,
|
||||
) -> ExtractedCardInfo {
|
||||
match (&card.co_badged_card_data, &card.card_isin) {
|
||||
(Some(co_badged), _) => {
|
||||
logger::debug!("Co-badged card data found in saved payment method");
|
||||
ExtractedCardInfo::new(Some(co_badged.clone()), card.card_type.clone(), None)
|
||||
}
|
||||
(None, Some(card_isin)) => {
|
||||
logger::debug!("No co-badged data; using saved card ISIN");
|
||||
ExtractedCardInfo::new(None, None, Some(Secret::new(card_isin.clone())))
|
||||
}
|
||||
_ => ExtractedCardInfo::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_from_payment_method_data<F, D>(payment_data: &D) -> ExtractedCardInfo
|
||||
where
|
||||
D: OperationSessionGetters<F>,
|
||||
{
|
||||
match payment_data.get_payment_method_data() {
|
||||
Some(hyperswitch_domain_models::payment_method_data::PaymentMethodData::Card(card)) => {
|
||||
logger::debug!("Using card data from payment request");
|
||||
ExtractedCardInfo::new(
|
||||
None,
|
||||
None,
|
||||
Some(Secret::new(card.card_number.get_extended_card_bin())),
|
||||
)
|
||||
}
|
||||
Some(hyperswitch_domain_models::payment_method_data::PaymentMethodData::Wallet(
|
||||
wallet_data,
|
||||
)) => extract_from_wallet_data(wallet_data, payment_data),
|
||||
_ => ExtractedCardInfo::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_from_wallet_data<F, D>(
|
||||
wallet_data: &hyperswitch_domain_models::payment_method_data::WalletData,
|
||||
payment_data: &D,
|
||||
) -> ExtractedCardInfo
|
||||
where
|
||||
D: OperationSessionGetters<F>,
|
||||
{
|
||||
match wallet_data {
|
||||
hyperswitch_domain_models::payment_method_data::WalletData::ApplePay(_) => {
|
||||
logger::debug!("Using Apple Pay data from payment request");
|
||||
let apple_pay_isin = extract_apple_pay_isin(payment_data);
|
||||
ExtractedCardInfo::new(None, None, apple_pay_isin)
|
||||
}
|
||||
_ => ExtractedCardInfo::empty(),
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_apple_pay_isin<F, D>(payment_data: &D) -> Option<Secret<String>>
|
||||
where
|
||||
D: OperationSessionGetters<F>,
|
||||
{
|
||||
payment_data.get_payment_method_token().and_then(|token| {
|
||||
if let hyperswitch_domain_models::router_data::PaymentMethodToken::ApplePayDecrypt(
|
||||
apple_pay_decrypt_data,
|
||||
) = token
|
||||
{
|
||||
logger::debug!("Using Apple Pay decrypt data from payment method token");
|
||||
Some(Secret::new(
|
||||
apple_pay_decrypt_data
|
||||
.application_primary_account_number
|
||||
.peek()
|
||||
.chars()
|
||||
.take(8)
|
||||
.collect::<String>(),
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_retryable_connector<F, D>(
|
||||
|
||||
@ -9916,10 +9916,24 @@ impl<F: Clone> OperationSessionSetters<F> for PaymentData<F> {
|
||||
}
|
||||
|
||||
fn set_card_network(&mut self, card_network: enums::CardNetwork) {
|
||||
if let Some(domain::PaymentMethodData::Card(card)) = &mut self.payment_method_data {
|
||||
logger::debug!("set card network {:?}", card_network.clone());
|
||||
card.card_network = Some(card_network);
|
||||
};
|
||||
match &mut self.payment_method_data {
|
||||
Some(domain::PaymentMethodData::Card(card)) => {
|
||||
logger::debug!("Setting card network: {:?}", card_network);
|
||||
card.card_network = Some(card_network);
|
||||
}
|
||||
Some(domain::PaymentMethodData::Wallet(wallet_data)) => match wallet_data {
|
||||
hyperswitch_domain_models::payment_method_data::WalletData::ApplePay(wallet) => {
|
||||
logger::debug!("Setting Apple Pay card network: {:?}", card_network);
|
||||
wallet.payment_method.network = card_network.to_string();
|
||||
}
|
||||
_ => {
|
||||
logger::debug!("Wallet type does not support setting card network.");
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
logger::warn!("Payment method data does not support setting card network.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_co_badged_card_data(
|
||||
|
||||
Reference in New Issue
Block a user