mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-01 11:06:50 +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,
|
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,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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,
|
||||||
|
|||||||
@ -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(
|
||||||
|
|||||||
@ -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
|
||||||
common_enums::PaymentMethod::Card,
|
Ok((
|
||||||
common_enums::PaymentMethodType::Credit,
|
common_enums::PaymentMethod::Card,
|
||||||
Some(card_network),
|
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
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,
|
||||||
|
|||||||
@ -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(())
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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,35 +643,38 @@ impl PaymentCreate {
|
|||||||
utils::get_payment_attempt_id(payment_id, 1)
|
utils::get_payment_attempt_id(payment_id, 1)
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(storage::PaymentAttemptNew {
|
Ok((
|
||||||
payment_id: payment_id.to_string(),
|
storage::PaymentAttemptNew {
|
||||||
merchant_id: merchant_id.to_string(),
|
payment_id: payment_id.to_string(),
|
||||||
attempt_id,
|
merchant_id: merchant_id.to_string(),
|
||||||
status,
|
attempt_id,
|
||||||
currency,
|
status,
|
||||||
amount: amount.into(),
|
currency,
|
||||||
payment_method,
|
amount: amount.into(),
|
||||||
capture_method: request.capture_method,
|
payment_method,
|
||||||
capture_on: request.capture_on,
|
capture_method: request.capture_method,
|
||||||
confirm: request.confirm.unwrap_or(false),
|
capture_on: request.capture_on,
|
||||||
created_at,
|
confirm: request.confirm.unwrap_or(false),
|
||||||
modified_at,
|
created_at,
|
||||||
last_synced,
|
modified_at,
|
||||||
authentication_type: request.authentication_type,
|
last_synced,
|
||||||
browser_info,
|
authentication_type: request.authentication_type,
|
||||||
payment_experience: request.payment_experience,
|
browser_info,
|
||||||
payment_method_type,
|
payment_experience: request.payment_experience,
|
||||||
payment_method_data: additional_pm_data,
|
payment_method_type,
|
||||||
amount_to_capture: request.amount_to_capture,
|
payment_method_data: additional_pm_data_value,
|
||||||
payment_token: request.payment_token.clone(),
|
amount_to_capture: request.amount_to_capture,
|
||||||
mandate_id: request.mandate_id.clone(),
|
payment_token: request.payment_token.clone(),
|
||||||
business_sub_label: request.business_sub_label.clone(),
|
mandate_id: request.mandate_id.clone(),
|
||||||
mandate_details: request
|
business_sub_label: request.business_sub_label.clone(),
|
||||||
.mandate_data
|
mandate_details: request
|
||||||
.as_ref()
|
.mandate_data
|
||||||
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
|
.as_ref()
|
||||||
..storage::PaymentAttemptNew::default()
|
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
|
||||||
})
|
..storage::PaymentAttemptNew::default()
|
||||||
|
},
|
||||||
|
additional_pm_data,
|
||||||
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip_all)]
|
#[instrument(skip_all)]
|
||||||
|
|||||||
@ -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,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user