mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 04:04:43 +08:00
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:
@ -342,6 +342,8 @@ pub struct SurchargeDetailsResponse {
|
||||
pub display_surcharge_amount: f64,
|
||||
/// tax on surcharge amount for this payment
|
||||
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,
|
||||
pub display_final_amount: f64,
|
||||
}
|
||||
|
||||
@ -709,6 +709,33 @@ pub struct Card {
|
||||
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)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct CardToken {
|
||||
@ -882,6 +909,21 @@ impl PaymentMethodData {
|
||||
| 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 {
|
||||
|
||||
@ -30,7 +30,6 @@ impl EuclidDirFilter for SurchargeDecisionConfigs {
|
||||
DirKeyKind::PaymentAmount,
|
||||
DirKeyKind::PaymentCurrency,
|
||||
DirKeyKind::BillingCountry,
|
||||
DirKeyKind::CardType,
|
||||
DirKeyKind::CardNetwork,
|
||||
DirKeyKind::PayLaterType,
|
||||
DirKeyKind::WalletType,
|
||||
|
||||
@ -13,10 +13,7 @@ pub mod types;
|
||||
|
||||
use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant, vec::IntoIter};
|
||||
|
||||
use api_models::{
|
||||
self, enums,
|
||||
payments::{self, HeaderPayload},
|
||||
};
|
||||
use api_models::{self, enums, payments::HeaderPayload};
|
||||
use common_utils::{ext_traits::AsyncExt, pii, types::Surcharge};
|
||||
use data_models::mandates::MandateData;
|
||||
use diesel_models::{ephemeral_key, fraud_check::FraudCheck};
|
||||
@ -176,10 +173,6 @@ where
|
||||
let mut connector_http_status_code = None;
|
||||
let mut external_latency = None;
|
||||
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 {
|
||||
api::ConnectorCallType::PreDetermined(connector) => {
|
||||
let schedule_time = if should_add_task_to_process_tracker {
|
||||
@ -406,7 +399,6 @@ where
|
||||
async fn populate_surcharge_details<F>(
|
||||
state: &AppState,
|
||||
payment_data: &mut PaymentData<F>,
|
||||
request: &payments::PaymentsRequest,
|
||||
) -> RouterResult<()>
|
||||
where
|
||||
F: Send + Clone,
|
||||
@ -416,7 +408,7 @@ where
|
||||
.surcharge_applicable
|
||||
.unwrap_or(false)
|
||||
{
|
||||
let payment_method_data = request
|
||||
let payment_method_data = payment_data
|
||||
.payment_method_data
|
||||
.clone()
|
||||
.get_required_value("payment_method_data")?;
|
||||
@ -437,39 +429,7 @@ where
|
||||
Err(err) => Err(err).change_context(errors::ApiErrorResponse::InternalServerError)?,
|
||||
};
|
||||
|
||||
let request_surcharge_details = request.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(()),
|
||||
};
|
||||
payment_data.surcharge_details = calculated_surcharge_details;
|
||||
} else {
|
||||
let surcharge_details =
|
||||
payment_data
|
||||
@ -978,6 +938,10 @@ where
|
||||
payment_data,
|
||||
)
|
||||
.await?;
|
||||
operation
|
||||
.to_domain()?
|
||||
.populate_payment_data(state, payment_data, merchant_account)
|
||||
.await?;
|
||||
|
||||
let mut router_data = payment_data
|
||||
.construct_router_data(
|
||||
|
||||
@ -3634,31 +3634,16 @@ pub fn get_key_params_for_surcharge_details(
|
||||
)> {
|
||||
match payment_method_data {
|
||||
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
|
||||
.card_network
|
||||
.get_required_value("payment_method_data.card.card_network")?;
|
||||
match card_type.to_lowercase().as_str() {
|
||||
"credit" => Ok((
|
||||
common_enums::PaymentMethod::Card,
|
||||
common_enums::PaymentMethodType::Credit,
|
||||
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())
|
||||
}
|
||||
}
|
||||
// surcharge generated will always be same for credit as well as debit
|
||||
// since surcharge conditions cannot be defined on card_type
|
||||
Ok((
|
||||
common_enums::PaymentMethod::Card,
|
||||
common_enums::PaymentMethodType::Credit,
|
||||
Some(card_network),
|
||||
))
|
||||
}
|
||||
api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok((
|
||||
common_enums::PaymentMethod::CardRedirect,
|
||||
|
||||
@ -159,7 +159,6 @@ pub trait Domain<F: Clone, R, Ctx: PaymentMethodRetrieve>: Send + Sync {
|
||||
&'a self,
|
||||
_state: &AppState,
|
||||
_payment_data: &mut PaymentData<F>,
|
||||
_request: &R,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
Ok(())
|
||||
|
||||
@ -446,6 +446,21 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
||||
)
|
||||
.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 {
|
||||
flow: PhantomData,
|
||||
payment_intent,
|
||||
@ -462,7 +477,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
||||
billing: billing_address.as_ref().map(|a| a.into()),
|
||||
},
|
||||
confirm: request.confirm,
|
||||
payment_method_data: request.payment_method_data.clone(),
|
||||
payment_method_data: payment_method_data_after_card_bin_call,
|
||||
force_sync: None,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
@ -593,10 +608,9 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
|
||||
&'a self,
|
||||
state: &AppState,
|
||||
payment_data: &mut PaymentData<F>,
|
||||
request: &api::PaymentsRequest,
|
||||
_merchant_account: &domain::MerchantAccount,
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
populate_surcharge_details(state, payment_data, request).await
|
||||
populate_surcharge_details(state, payment_data).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -167,7 +167,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
||||
)
|
||||
.await?;
|
||||
|
||||
let payment_attempt_new = Self::make_payment_attempt(
|
||||
let (payment_attempt_new, additional_payment_data) = Self::make_payment_attempt(
|
||||
&payment_id,
|
||||
merchant_id,
|
||||
money,
|
||||
@ -290,6 +290,14 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
||||
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 {
|
||||
flow: PhantomData,
|
||||
payment_intent,
|
||||
@ -306,7 +314,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
|
||||
billing: billing_address.as_ref().map(|a| a.into()),
|
||||
},
|
||||
confirm: request.confirm,
|
||||
payment_method_data: request.payment_method_data.clone(),
|
||||
payment_method_data: payment_method_data_after_card_bin_call,
|
||||
refunds: vec![],
|
||||
disputes: vec![],
|
||||
attempts: None,
|
||||
@ -604,7 +612,10 @@ impl PaymentCreate {
|
||||
request: &api::PaymentsRequest,
|
||||
browser_info: Option<serde_json::Value>,
|
||||
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 status =
|
||||
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
|
||||
@ -616,7 +627,8 @@ impl PaymentCreate {
|
||||
.async_map(|payment_method_data| async {
|
||||
helpers::get_additional_payment_data(payment_method_data, &*state.store).await
|
||||
})
|
||||
.await
|
||||
.await;
|
||||
let additional_pm_data_value = additional_pm_data
|
||||
.as_ref()
|
||||
.map(Encode::<api_models::payments::AdditionalPaymentData>::encode_to_value)
|
||||
.transpose()
|
||||
@ -631,35 +643,38 @@ impl PaymentCreate {
|
||||
utils::get_payment_attempt_id(payment_id, 1)
|
||||
};
|
||||
|
||||
Ok(storage::PaymentAttemptNew {
|
||||
payment_id: payment_id.to_string(),
|
||||
merchant_id: merchant_id.to_string(),
|
||||
attempt_id,
|
||||
status,
|
||||
currency,
|
||||
amount: amount.into(),
|
||||
payment_method,
|
||||
capture_method: request.capture_method,
|
||||
capture_on: request.capture_on,
|
||||
confirm: request.confirm.unwrap_or(false),
|
||||
created_at,
|
||||
modified_at,
|
||||
last_synced,
|
||||
authentication_type: request.authentication_type,
|
||||
browser_info,
|
||||
payment_experience: request.payment_experience,
|
||||
payment_method_type,
|
||||
payment_method_data: additional_pm_data,
|
||||
amount_to_capture: request.amount_to_capture,
|
||||
payment_token: request.payment_token.clone(),
|
||||
mandate_id: request.mandate_id.clone(),
|
||||
business_sub_label: request.business_sub_label.clone(),
|
||||
mandate_details: request
|
||||
.mandate_data
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
|
||||
..storage::PaymentAttemptNew::default()
|
||||
})
|
||||
Ok((
|
||||
storage::PaymentAttemptNew {
|
||||
payment_id: payment_id.to_string(),
|
||||
merchant_id: merchant_id.to_string(),
|
||||
attempt_id,
|
||||
status,
|
||||
currency,
|
||||
amount: amount.into(),
|
||||
payment_method,
|
||||
capture_method: request.capture_method,
|
||||
capture_on: request.capture_on,
|
||||
confirm: request.confirm.unwrap_or(false),
|
||||
created_at,
|
||||
modified_at,
|
||||
last_synced,
|
||||
authentication_type: request.authentication_type,
|
||||
browser_info,
|
||||
payment_experience: request.payment_experience,
|
||||
payment_method_type,
|
||||
payment_method_data: additional_pm_data_value,
|
||||
amount_to_capture: request.amount_to_capture,
|
||||
payment_token: request.payment_token.clone(),
|
||||
mandate_id: request.mandate_id.clone(),
|
||||
business_sub_label: request.business_sub_label.clone(),
|
||||
mandate_details: request
|
||||
.mandate_data
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
|
||||
..storage::PaymentAttemptNew::default()
|
||||
},
|
||||
additional_pm_data,
|
||||
))
|
||||
}
|
||||
|
||||
#[instrument(skip_all)]
|
||||
|
||||
@ -219,6 +219,8 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe
|
||||
tax_on_surcharge: surcharge_details.tax_on_surcharge.clone(),
|
||||
display_surcharge_amount,
|
||||
display_tax_on_surcharge_amount,
|
||||
display_total_surcharge_amount: display_surcharge_amount
|
||||
+ display_tax_on_surcharge_amount,
|
||||
display_final_amount,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user