mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-11-01 02:57:02 +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 | ||||||
|  |             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, | ||||||
|  | |||||||
| @ -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,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)] | ||||||
|  | |||||||
| @ -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
	 Hrithikesh
					Hrithikesh