mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +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(( | ||||
|             // 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), | ||||
|                 )), | ||||
|                 "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(( | ||||
|             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,7 +643,8 @@ impl PaymentCreate { | ||||
|             utils::get_payment_attempt_id(payment_id, 1) | ||||
|         }; | ||||
|  | ||||
|         Ok(storage::PaymentAttemptNew { | ||||
|         Ok(( | ||||
|             storage::PaymentAttemptNew { | ||||
|                 payment_id: payment_id.to_string(), | ||||
|                 merchant_id: merchant_id.to_string(), | ||||
|                 attempt_id, | ||||
| @ -649,7 +662,7 @@ impl PaymentCreate { | ||||
|                 browser_info, | ||||
|                 payment_experience: request.payment_experience, | ||||
|                 payment_method_type, | ||||
|             payment_method_data: additional_pm_data, | ||||
|                 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(), | ||||
| @ -659,7 +672,9 @@ impl PaymentCreate { | ||||
|                     .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
	 Hrithikesh
					Hrithikesh