fix: use card bin to get additional card details (#3036)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
Hrithikesh
2023-12-04 19:51:41 +05:30
committed by GitHub
parent a0cfdd3fb1
commit 6c7d3a2e8a
9 changed files with 125 additions and 103 deletions

View File

@ -342,6 +342,8 @@ pub struct SurchargeDetailsResponse {
pub display_surcharge_amount: f64, pub display_surcharge_amount: f64,
/// tax on surcharge amount for this payment /// tax on surcharge amount for this payment
pub display_tax_on_surcharge_amount: f64, pub display_tax_on_surcharge_amount: f64,
/// sum of display_surcharge_amount and display_tax_on_surcharge_amount
pub display_total_surcharge_amount: f64,
/// sum of original amount, /// sum of original amount,
pub display_final_amount: f64, pub display_final_amount: f64,
} }

View File

@ -709,6 +709,33 @@ pub struct Card {
pub nick_name: Option<Secret<String>>, pub nick_name: Option<Secret<String>>,
} }
impl Card {
fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self {
Self {
card_number: self.card_number.clone(),
card_exp_month: self.card_exp_month.clone(),
card_exp_year: self.card_exp_year.clone(),
card_holder_name: self.card_holder_name.clone(),
card_cvc: self.card_cvc.clone(),
card_issuer: self
.card_issuer
.clone()
.or(additional_card_info.card_issuer),
card_network: self
.card_network
.clone()
.or(additional_card_info.card_network),
card_type: self.card_type.clone().or(additional_card_info.card_type),
card_issuing_country: self
.card_issuing_country
.clone()
.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(),
}
}
}
#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, Default)] #[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, Default)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct CardToken { pub struct CardToken {
@ -882,6 +909,21 @@ impl PaymentMethodData {
| Self::CardToken(_) => None, | Self::CardToken(_) => None,
} }
} }
pub fn apply_additional_payment_data(
&self,
additional_payment_data: AdditionalPaymentData,
) -> Self {
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(),
}
} else {
self.to_owned()
}
}
} }
pub trait GetPaymentMethodType { pub trait GetPaymentMethodType {

View File

@ -30,7 +30,6 @@ impl EuclidDirFilter for SurchargeDecisionConfigs {
DirKeyKind::PaymentAmount, DirKeyKind::PaymentAmount,
DirKeyKind::PaymentCurrency, DirKeyKind::PaymentCurrency,
DirKeyKind::BillingCountry, DirKeyKind::BillingCountry,
DirKeyKind::CardType,
DirKeyKind::CardNetwork, DirKeyKind::CardNetwork,
DirKeyKind::PayLaterType, DirKeyKind::PayLaterType,
DirKeyKind::WalletType, DirKeyKind::WalletType,

View File

@ -13,10 +13,7 @@ pub mod types;
use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant, vec::IntoIter}; use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant, vec::IntoIter};
use api_models::{ use api_models::{self, enums, payments::HeaderPayload};
self, enums,
payments::{self, HeaderPayload},
};
use common_utils::{ext_traits::AsyncExt, pii, types::Surcharge}; use common_utils::{ext_traits::AsyncExt, pii, types::Surcharge};
use data_models::mandates::MandateData; use data_models::mandates::MandateData;
use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use diesel_models::{ephemeral_key, fraud_check::FraudCheck};
@ -176,10 +173,6 @@ where
let mut connector_http_status_code = None; let mut connector_http_status_code = None;
let mut external_latency = None; let mut external_latency = None;
if let Some(connector_details) = connector { if let Some(connector_details) = connector {
operation
.to_domain()?
.populate_payment_data(state, &mut payment_data, &req, &merchant_account)
.await?;
payment_data = match connector_details { payment_data = match connector_details {
api::ConnectorCallType::PreDetermined(connector) => { api::ConnectorCallType::PreDetermined(connector) => {
let schedule_time = if should_add_task_to_process_tracker { let schedule_time = if should_add_task_to_process_tracker {
@ -406,7 +399,6 @@ where
async fn populate_surcharge_details<F>( async fn populate_surcharge_details<F>(
state: &AppState, state: &AppState,
payment_data: &mut PaymentData<F>, payment_data: &mut PaymentData<F>,
request: &payments::PaymentsRequest,
) -> RouterResult<()> ) -> RouterResult<()>
where where
F: Send + Clone, F: Send + Clone,
@ -416,7 +408,7 @@ where
.surcharge_applicable .surcharge_applicable
.unwrap_or(false) .unwrap_or(false)
{ {
let payment_method_data = request let payment_method_data = payment_data
.payment_method_data .payment_method_data
.clone() .clone()
.get_required_value("payment_method_data")?; .get_required_value("payment_method_data")?;
@ -437,39 +429,7 @@ where
Err(err) => Err(err).change_context(errors::ApiErrorResponse::InternalServerError)?, Err(err) => Err(err).change_context(errors::ApiErrorResponse::InternalServerError)?,
}; };
let request_surcharge_details = request.surcharge_details; payment_data.surcharge_details = calculated_surcharge_details;
match (request_surcharge_details, calculated_surcharge_details) {
(Some(request_surcharge_details), Some(calculated_surcharge_details)) => {
if calculated_surcharge_details
.is_request_surcharge_matching(request_surcharge_details)
{
payment_data.surcharge_details = Some(calculated_surcharge_details);
} else {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(),
}
.into());
}
}
(None, Some(_calculated_surcharge_details)) => {
return Err(errors::ApiErrorResponse::MissingRequiredField {
field_name: "surcharge_details",
}
.into());
}
(Some(request_surcharge_details), None) => {
if request_surcharge_details.is_surcharge_zero() {
return Ok(());
} else {
return Err(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(),
}
.into());
}
}
(None, None) => return Ok(()),
};
} else { } else {
let surcharge_details = let surcharge_details =
payment_data payment_data
@ -978,6 +938,10 @@ where
payment_data, payment_data,
) )
.await?; .await?;
operation
.to_domain()?
.populate_payment_data(state, payment_data, merchant_account)
.await?;
let mut router_data = payment_data let mut router_data = payment_data
.construct_router_data( .construct_router_data(

View File

@ -3634,31 +3634,16 @@ pub fn get_key_params_for_surcharge_details(
)> { )> {
match payment_method_data { match payment_method_data {
api_models::payments::PaymentMethodData::Card(card) => { api_models::payments::PaymentMethodData::Card(card) => {
let card_type = card
.card_type
.get_required_value("payment_method_data.card.card_type")?;
let card_network = card let card_network = card
.card_network .card_network
.get_required_value("payment_method_data.card.card_network")?; .get_required_value("payment_method_data.card.card_network")?;
match card_type.to_lowercase().as_str() { // surcharge generated will always be same for credit as well as debit
"credit" => Ok(( // since surcharge conditions cannot be defined on card_type
Ok((
common_enums::PaymentMethod::Card, common_enums::PaymentMethod::Card,
common_enums::PaymentMethodType::Credit, common_enums::PaymentMethodType::Credit,
Some(card_network), Some(card_network),
)), ))
"debit" => Ok((
common_enums::PaymentMethod::Card,
common_enums::PaymentMethodType::Debit,
Some(card_network),
)),
_ => {
logger::debug!("Invalid Card type found in payment confirm call, hence surcharge not applicable");
Err(errors::ApiErrorResponse::InvalidDataValue {
field_name: "payment_method_data.card.card_type",
}
.into())
}
}
} }
api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok(( api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok((
common_enums::PaymentMethod::CardRedirect, common_enums::PaymentMethod::CardRedirect,

View File

@ -159,7 +159,6 @@ pub trait Domain<F: Clone, R, Ctx: PaymentMethodRetrieve>: Send + Sync {
&'a self, &'a self,
_state: &AppState, _state: &AppState,
_payment_data: &mut PaymentData<F>, _payment_data: &mut PaymentData<F>,
_request: &R,
_merchant_account: &domain::MerchantAccount, _merchant_account: &domain::MerchantAccount,
) -> CustomResult<(), errors::ApiErrorResponse> { ) -> CustomResult<(), errors::ApiErrorResponse> {
Ok(()) Ok(())

View File

@ -446,6 +446,21 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
) )
.await?; .await?;
let additional_pm_data = request
.payment_method_data
.as_ref()
.async_map(|payment_method_data| async {
helpers::get_additional_payment_data(payment_method_data, &*state.store).await
})
.await;
let payment_method_data_after_card_bin_call = request
.payment_method_data
.as_ref()
.zip(additional_pm_data)
.map(|(payment_method_data, additional_payment_data)| {
payment_method_data.apply_additional_payment_data(additional_payment_data)
});
let payment_data = PaymentData { let payment_data = PaymentData {
flow: PhantomData, flow: PhantomData,
payment_intent, payment_intent,
@ -462,7 +477,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
billing: billing_address.as_ref().map(|a| a.into()), billing: billing_address.as_ref().map(|a| a.into()),
}, },
confirm: request.confirm, confirm: request.confirm,
payment_method_data: request.payment_method_data.clone(), payment_method_data: payment_method_data_after_card_bin_call,
force_sync: None, force_sync: None,
refunds: vec![], refunds: vec![],
disputes: vec![], disputes: vec![],
@ -593,10 +608,9 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
&'a self, &'a self,
state: &AppState, state: &AppState,
payment_data: &mut PaymentData<F>, payment_data: &mut PaymentData<F>,
request: &api::PaymentsRequest,
_merchant_account: &domain::MerchantAccount, _merchant_account: &domain::MerchantAccount,
) -> CustomResult<(), errors::ApiErrorResponse> { ) -> CustomResult<(), errors::ApiErrorResponse> {
populate_surcharge_details(state, payment_data, request).await populate_surcharge_details(state, payment_data).await
} }
} }

View File

@ -167,7 +167,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
) )
.await?; .await?;
let payment_attempt_new = Self::make_payment_attempt( let (payment_attempt_new, additional_payment_data) = Self::make_payment_attempt(
&payment_id, &payment_id,
merchant_id, merchant_id,
money, money,
@ -290,6 +290,14 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
payments::SurchargeDetails::from((&request_surcharge_details, &payment_attempt)) payments::SurchargeDetails::from((&request_surcharge_details, &payment_attempt))
}); });
let payment_method_data_after_card_bin_call = request
.payment_method_data
.as_ref()
.zip(additional_payment_data)
.map(|(payment_method_data, additional_payment_data)| {
payment_method_data.apply_additional_payment_data(additional_payment_data)
});
let payment_data = PaymentData { let payment_data = PaymentData {
flow: PhantomData, flow: PhantomData,
payment_intent, payment_intent,
@ -306,7 +314,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
billing: billing_address.as_ref().map(|a| a.into()), billing: billing_address.as_ref().map(|a| a.into()),
}, },
confirm: request.confirm, confirm: request.confirm,
payment_method_data: request.payment_method_data.clone(), payment_method_data: payment_method_data_after_card_bin_call,
refunds: vec![], refunds: vec![],
disputes: vec![], disputes: vec![],
attempts: None, attempts: None,
@ -604,7 +612,10 @@ impl PaymentCreate {
request: &api::PaymentsRequest, request: &api::PaymentsRequest,
browser_info: Option<serde_json::Value>, browser_info: Option<serde_json::Value>,
state: &AppState, state: &AppState,
) -> RouterResult<storage::PaymentAttemptNew> { ) -> RouterResult<(
storage::PaymentAttemptNew,
Option<api_models::payments::AdditionalPaymentData>,
)> {
let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now());
let status = let status =
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
@ -616,7 +627,8 @@ impl PaymentCreate {
.async_map(|payment_method_data| async { .async_map(|payment_method_data| async {
helpers::get_additional_payment_data(payment_method_data, &*state.store).await helpers::get_additional_payment_data(payment_method_data, &*state.store).await
}) })
.await .await;
let additional_pm_data_value = additional_pm_data
.as_ref() .as_ref()
.map(Encode::<api_models::payments::AdditionalPaymentData>::encode_to_value) .map(Encode::<api_models::payments::AdditionalPaymentData>::encode_to_value)
.transpose() .transpose()
@ -631,7 +643,8 @@ impl PaymentCreate {
utils::get_payment_attempt_id(payment_id, 1) utils::get_payment_attempt_id(payment_id, 1)
}; };
Ok(storage::PaymentAttemptNew { Ok((
storage::PaymentAttemptNew {
payment_id: payment_id.to_string(), payment_id: payment_id.to_string(),
merchant_id: merchant_id.to_string(), merchant_id: merchant_id.to_string(),
attempt_id, attempt_id,
@ -649,7 +662,7 @@ impl PaymentCreate {
browser_info, browser_info,
payment_experience: request.payment_experience, payment_experience: request.payment_experience,
payment_method_type, payment_method_type,
payment_method_data: additional_pm_data, payment_method_data: additional_pm_data_value,
amount_to_capture: request.amount_to_capture, amount_to_capture: request.amount_to_capture,
payment_token: request.payment_token.clone(), payment_token: request.payment_token.clone(),
mandate_id: request.mandate_id.clone(), mandate_id: request.mandate_id.clone(),
@ -659,7 +672,9 @@ impl PaymentCreate {
.as_ref() .as_ref()
.and_then(|inner| inner.mandate_type.clone().map(Into::into)), .and_then(|inner| inner.mandate_type.clone().map(Into::into)),
..storage::PaymentAttemptNew::default() ..storage::PaymentAttemptNew::default()
}) },
additional_pm_data,
))
} }
#[instrument(skip_all)] #[instrument(skip_all)]

View File

@ -219,6 +219,8 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe
tax_on_surcharge: surcharge_details.tax_on_surcharge.clone(), tax_on_surcharge: surcharge_details.tax_on_surcharge.clone(),
display_surcharge_amount, display_surcharge_amount,
display_tax_on_surcharge_amount, display_tax_on_surcharge_amount,
display_total_surcharge_amount: display_surcharge_amount
+ display_tax_on_surcharge_amount,
display_final_amount, display_final_amount,
}) })
} }