Skip to content

Commit 6c7d3a2

Browse files
fix: use card bin to get additional card details (#3036)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
1 parent a0cfdd3 commit 6c7d3a2

File tree

9 files changed

+125
-103
lines changed

9 files changed

+125
-103
lines changed

crates/api_models/src/payment_methods.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,8 @@ pub struct SurchargeDetailsResponse {
342342
pub display_surcharge_amount: f64,
343343
/// tax on surcharge amount for this payment
344344
pub display_tax_on_surcharge_amount: f64,
345+
/// sum of display_surcharge_amount and display_tax_on_surcharge_amount
346+
pub display_total_surcharge_amount: f64,
345347
/// sum of original amount,
346348
pub display_final_amount: f64,
347349
}

crates/api_models/src/payments.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,33 @@ pub struct Card {
709709
pub nick_name: Option<Secret<String>>,
710710
}
711711

712+
impl Card {
713+
fn apply_additional_card_info(&self, additional_card_info: AdditionalCardInfo) -> Self {
714+
Self {
715+
card_number: self.card_number.clone(),
716+
card_exp_month: self.card_exp_month.clone(),
717+
card_exp_year: self.card_exp_year.clone(),
718+
card_holder_name: self.card_holder_name.clone(),
719+
card_cvc: self.card_cvc.clone(),
720+
card_issuer: self
721+
.card_issuer
722+
.clone()
723+
.or(additional_card_info.card_issuer),
724+
card_network: self
725+
.card_network
726+
.clone()
727+
.or(additional_card_info.card_network),
728+
card_type: self.card_type.clone().or(additional_card_info.card_type),
729+
card_issuing_country: self
730+
.card_issuing_country
731+
.clone()
732+
.or(additional_card_info.card_issuing_country),
733+
bank_code: self.bank_code.clone().or(additional_card_info.bank_code),
734+
nick_name: self.nick_name.clone(),
735+
}
736+
}
737+
}
738+
712739
#[derive(Eq, PartialEq, Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, Default)]
713740
#[serde(rename_all = "snake_case")]
714741
pub struct CardToken {
@@ -882,6 +909,21 @@ impl PaymentMethodData {
882909
| Self::CardToken(_) => None,
883910
}
884911
}
912+
pub fn apply_additional_payment_data(
913+
&self,
914+
additional_payment_data: AdditionalPaymentData,
915+
) -> Self {
916+
if let AdditionalPaymentData::Card(additional_card_info) = additional_payment_data {
917+
match self {
918+
Self::Card(card) => {
919+
Self::Card(card.apply_additional_card_info(*additional_card_info))
920+
}
921+
_ => self.to_owned(),
922+
}
923+
} else {
924+
self.to_owned()
925+
}
926+
}
885927
}
886928

887929
pub trait GetPaymentMethodType {

crates/api_models/src/surcharge_decision_configs.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,6 @@ impl EuclidDirFilter for SurchargeDecisionConfigs {
3030
DirKeyKind::PaymentAmount,
3131
DirKeyKind::PaymentCurrency,
3232
DirKeyKind::BillingCountry,
33-
DirKeyKind::CardType,
3433
DirKeyKind::CardNetwork,
3534
DirKeyKind::PayLaterType,
3635
DirKeyKind::WalletType,

crates/router/src/core/payments.rs

Lines changed: 7 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,7 @@ pub mod types;
1313

1414
use std::{fmt::Debug, marker::PhantomData, ops::Deref, time::Instant, vec::IntoIter};
1515

16-
use api_models::{
17-
self, enums,
18-
payments::{self, HeaderPayload},
19-
};
16+
use api_models::{self, enums, payments::HeaderPayload};
2017
use common_utils::{ext_traits::AsyncExt, pii, types::Surcharge};
2118
use data_models::mandates::MandateData;
2219
use diesel_models::{ephemeral_key, fraud_check::FraudCheck};
@@ -176,10 +173,6 @@ where
176173
let mut connector_http_status_code = None;
177174
let mut external_latency = None;
178175
if let Some(connector_details) = connector {
179-
operation
180-
.to_domain()?
181-
.populate_payment_data(state, &mut payment_data, &req, &merchant_account)
182-
.await?;
183176
payment_data = match connector_details {
184177
api::ConnectorCallType::PreDetermined(connector) => {
185178
let schedule_time = if should_add_task_to_process_tracker {
@@ -406,7 +399,6 @@ where
406399
async fn populate_surcharge_details<F>(
407400
state: &AppState,
408401
payment_data: &mut PaymentData<F>,
409-
request: &payments::PaymentsRequest,
410402
) -> RouterResult<()>
411403
where
412404
F: Send + Clone,
@@ -416,7 +408,7 @@ where
416408
.surcharge_applicable
417409
.unwrap_or(false)
418410
{
419-
let payment_method_data = request
411+
let payment_method_data = payment_data
420412
.payment_method_data
421413
.clone()
422414
.get_required_value("payment_method_data")?;
@@ -437,39 +429,7 @@ where
437429
Err(err) => Err(err).change_context(errors::ApiErrorResponse::InternalServerError)?,
438430
};
439431

440-
let request_surcharge_details = request.surcharge_details;
441-
442-
match (request_surcharge_details, calculated_surcharge_details) {
443-
(Some(request_surcharge_details), Some(calculated_surcharge_details)) => {
444-
if calculated_surcharge_details
445-
.is_request_surcharge_matching(request_surcharge_details)
446-
{
447-
payment_data.surcharge_details = Some(calculated_surcharge_details);
448-
} else {
449-
return Err(errors::ApiErrorResponse::InvalidRequestData {
450-
message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(),
451-
}
452-
.into());
453-
}
454-
}
455-
(None, Some(_calculated_surcharge_details)) => {
456-
return Err(errors::ApiErrorResponse::MissingRequiredField {
457-
field_name: "surcharge_details",
458-
}
459-
.into());
460-
}
461-
(Some(request_surcharge_details), None) => {
462-
if request_surcharge_details.is_surcharge_zero() {
463-
return Ok(());
464-
} else {
465-
return Err(errors::ApiErrorResponse::InvalidRequestData {
466-
message: "Invalid value provided: 'surcharge_details'. surcharge details provided do not match with surcharge details sent in payment_methods list response".to_string(),
467-
}
468-
.into());
469-
}
470-
}
471-
(None, None) => return Ok(()),
472-
};
432+
payment_data.surcharge_details = calculated_surcharge_details;
473433
} else {
474434
let surcharge_details =
475435
payment_data
@@ -978,6 +938,10 @@ where
978938
payment_data,
979939
)
980940
.await?;
941+
operation
942+
.to_domain()?
943+
.populate_payment_data(state, payment_data, merchant_account)
944+
.await?;
981945

982946
let mut router_data = payment_data
983947
.construct_router_data(

crates/router/src/core/payments/helpers.rs

Lines changed: 7 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3634,31 +3634,16 @@ pub fn get_key_params_for_surcharge_details(
36343634
)> {
36353635
match payment_method_data {
36363636
api_models::payments::PaymentMethodData::Card(card) => {
3637-
let card_type = card
3638-
.card_type
3639-
.get_required_value("payment_method_data.card.card_type")?;
36403637
let card_network = card
36413638
.card_network
36423639
.get_required_value("payment_method_data.card.card_network")?;
3643-
match card_type.to_lowercase().as_str() {
3644-
"credit" => Ok((
3645-
common_enums::PaymentMethod::Card,
3646-
common_enums::PaymentMethodType::Credit,
3647-
Some(card_network),
3648-
)),
3649-
"debit" => Ok((
3650-
common_enums::PaymentMethod::Card,
3651-
common_enums::PaymentMethodType::Debit,
3652-
Some(card_network),
3653-
)),
3654-
_ => {
3655-
logger::debug!("Invalid Card type found in payment confirm call, hence surcharge not applicable");
3656-
Err(errors::ApiErrorResponse::InvalidDataValue {
3657-
field_name: "payment_method_data.card.card_type",
3658-
}
3659-
.into())
3660-
}
3661-
}
3640+
// surcharge generated will always be same for credit as well as debit
3641+
// since surcharge conditions cannot be defined on card_type
3642+
Ok((
3643+
common_enums::PaymentMethod::Card,
3644+
common_enums::PaymentMethodType::Credit,
3645+
Some(card_network),
3646+
))
36623647
}
36633648
api_models::payments::PaymentMethodData::CardRedirect(card_redirect_data) => Ok((
36643649
common_enums::PaymentMethod::CardRedirect,

crates/router/src/core/payments/operations.rs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,6 @@ pub trait Domain<F: Clone, R, Ctx: PaymentMethodRetrieve>: Send + Sync {
159159
&'a self,
160160
_state: &AppState,
161161
_payment_data: &mut PaymentData<F>,
162-
_request: &R,
163162
_merchant_account: &domain::MerchantAccount,
164163
) -> CustomResult<(), errors::ApiErrorResponse> {
165164
Ok(())

crates/router/src/core/payments/operations/payment_confirm.rs

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -446,6 +446,21 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
446446
)
447447
.await?;
448448

449+
let additional_pm_data = request
450+
.payment_method_data
451+
.as_ref()
452+
.async_map(|payment_method_data| async {
453+
helpers::get_additional_payment_data(payment_method_data, &*state.store).await
454+
})
455+
.await;
456+
let payment_method_data_after_card_bin_call = request
457+
.payment_method_data
458+
.as_ref()
459+
.zip(additional_pm_data)
460+
.map(|(payment_method_data, additional_payment_data)| {
461+
payment_method_data.apply_additional_payment_data(additional_payment_data)
462+
});
463+
449464
let payment_data = PaymentData {
450465
flow: PhantomData,
451466
payment_intent,
@@ -462,7 +477,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
462477
billing: billing_address.as_ref().map(|a| a.into()),
463478
},
464479
confirm: request.confirm,
465-
payment_method_data: request.payment_method_data.clone(),
480+
payment_method_data: payment_method_data_after_card_bin_call,
466481
force_sync: None,
467482
refunds: vec![],
468483
disputes: vec![],
@@ -593,10 +608,9 @@ impl<F: Clone + Send, Ctx: PaymentMethodRetrieve> Domain<F, api::PaymentsRequest
593608
&'a self,
594609
state: &AppState,
595610
payment_data: &mut PaymentData<F>,
596-
request: &api::PaymentsRequest,
597611
_merchant_account: &domain::MerchantAccount,
598612
) -> CustomResult<(), errors::ApiErrorResponse> {
599-
populate_surcharge_details(state, payment_data, request).await
613+
populate_surcharge_details(state, payment_data).await
600614
}
601615
}
602616

crates/router/src/core/payments/operations/payment_create.rs

Lines changed: 48 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
167167
)
168168
.await?;
169169

170-
let payment_attempt_new = Self::make_payment_attempt(
170+
let (payment_attempt_new, additional_payment_data) = Self::make_payment_attempt(
171171
&payment_id,
172172
merchant_id,
173173
money,
@@ -290,6 +290,14 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
290290
payments::SurchargeDetails::from((&request_surcharge_details, &payment_attempt))
291291
});
292292

293+
let payment_method_data_after_card_bin_call = request
294+
.payment_method_data
295+
.as_ref()
296+
.zip(additional_payment_data)
297+
.map(|(payment_method_data, additional_payment_data)| {
298+
payment_method_data.apply_additional_payment_data(additional_payment_data)
299+
});
300+
293301
let payment_data = PaymentData {
294302
flow: PhantomData,
295303
payment_intent,
@@ -306,7 +314,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve>
306314
billing: billing_address.as_ref().map(|a| a.into()),
307315
},
308316
confirm: request.confirm,
309-
payment_method_data: request.payment_method_data.clone(),
317+
payment_method_data: payment_method_data_after_card_bin_call,
310318
refunds: vec![],
311319
disputes: vec![],
312320
attempts: None,
@@ -604,7 +612,10 @@ impl PaymentCreate {
604612
request: &api::PaymentsRequest,
605613
browser_info: Option<serde_json::Value>,
606614
state: &AppState,
607-
) -> RouterResult<storage::PaymentAttemptNew> {
615+
) -> RouterResult<(
616+
storage::PaymentAttemptNew,
617+
Option<api_models::payments::AdditionalPaymentData>,
618+
)> {
608619
let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now());
609620
let status =
610621
helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm);
@@ -616,7 +627,8 @@ impl PaymentCreate {
616627
.async_map(|payment_method_data| async {
617628
helpers::get_additional_payment_data(payment_method_data, &*state.store).await
618629
})
619-
.await
630+
.await;
631+
let additional_pm_data_value = additional_pm_data
620632
.as_ref()
621633
.map(Encode::<api_models::payments::AdditionalPaymentData>::encode_to_value)
622634
.transpose()
@@ -631,35 +643,38 @@ impl PaymentCreate {
631643
utils::get_payment_attempt_id(payment_id, 1)
632644
};
633645

634-
Ok(storage::PaymentAttemptNew {
635-
payment_id: payment_id.to_string(),
636-
merchant_id: merchant_id.to_string(),
637-
attempt_id,
638-
status,
639-
currency,
640-
amount: amount.into(),
641-
payment_method,
642-
capture_method: request.capture_method,
643-
capture_on: request.capture_on,
644-
confirm: request.confirm.unwrap_or(false),
645-
created_at,
646-
modified_at,
647-
last_synced,
648-
authentication_type: request.authentication_type,
649-
browser_info,
650-
payment_experience: request.payment_experience,
651-
payment_method_type,
652-
payment_method_data: additional_pm_data,
653-
amount_to_capture: request.amount_to_capture,
654-
payment_token: request.payment_token.clone(),
655-
mandate_id: request.mandate_id.clone(),
656-
business_sub_label: request.business_sub_label.clone(),
657-
mandate_details: request
658-
.mandate_data
659-
.as_ref()
660-
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
661-
..storage::PaymentAttemptNew::default()
662-
})
646+
Ok((
647+
storage::PaymentAttemptNew {
648+
payment_id: payment_id.to_string(),
649+
merchant_id: merchant_id.to_string(),
650+
attempt_id,
651+
status,
652+
currency,
653+
amount: amount.into(),
654+
payment_method,
655+
capture_method: request.capture_method,
656+
capture_on: request.capture_on,
657+
confirm: request.confirm.unwrap_or(false),
658+
created_at,
659+
modified_at,
660+
last_synced,
661+
authentication_type: request.authentication_type,
662+
browser_info,
663+
payment_experience: request.payment_experience,
664+
payment_method_type,
665+
payment_method_data: additional_pm_data_value,
666+
amount_to_capture: request.amount_to_capture,
667+
payment_token: request.payment_token.clone(),
668+
mandate_id: request.mandate_id.clone(),
669+
business_sub_label: request.business_sub_label.clone(),
670+
mandate_details: request
671+
.mandate_data
672+
.as_ref()
673+
.and_then(|inner| inner.mandate_type.clone().map(Into::into)),
674+
..storage::PaymentAttemptNew::default()
675+
},
676+
additional_pm_data,
677+
))
663678
}
664679

665680
#[instrument(skip_all)]

crates/router/src/core/payments/types.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,8 @@ impl ForeignTryFrom<(&SurchargeDetails, &PaymentAttempt)> for SurchargeDetailsRe
219219
tax_on_surcharge: surcharge_details.tax_on_surcharge.clone(),
220220
display_surcharge_amount,
221221
display_tax_on_surcharge_amount,
222+
display_total_surcharge_amount: display_surcharge_amount
223+
+ display_tax_on_surcharge_amount,
222224
display_final_amount,
223225
})
224226
}

0 commit comments

Comments
 (0)