mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	refactor(core): add locker config to enable or disable locker (#3352)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -54,6 +54,8 @@ impl Default for super::settings::Locker { | ||||
|             mock_locker: true, | ||||
|             basilisk_host: "localhost".into(), | ||||
|             locker_signing_key_id: "1".into(), | ||||
|             //true or false | ||||
|             locker_enabled: true, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -490,6 +490,7 @@ pub struct Locker { | ||||
|     pub mock_locker: bool, | ||||
|     pub basilisk_host: String, | ||||
|     pub locker_signing_key_id: String, | ||||
|     pub locker_enabled: bool, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone)] | ||||
|  | ||||
| @ -101,6 +101,10 @@ pub async fn call_to_locker( | ||||
|             card_exp_year: card.card_exp_year, | ||||
|             card_holder_name: card.name_on_card, | ||||
|             nick_name: card.nick_name.map(masking::Secret::new), | ||||
|             card_issuing_country: None, | ||||
|             card_network: None, | ||||
|             card_issuer: None, | ||||
|             card_type: None, | ||||
|         }; | ||||
|  | ||||
|         let pm_create = api::PaymentMethodCreate { | ||||
|  | ||||
| @ -33,6 +33,7 @@ use crate::{ | ||||
| pub async fn get_mandate( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     req: mandates::MandateId, | ||||
| ) -> RouterResponse<mandates::MandateResponse> { | ||||
|     let mandate = state | ||||
| @ -42,7 +43,7 @@ pub async fn get_mandate( | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; | ||||
|     Ok(services::ApplicationResponse::Json( | ||||
|         mandates::MandateResponse::from_db_mandate(&state, mandate).await?, | ||||
|         mandates::MandateResponse::from_db_mandate(&state, key_store, mandate).await?, | ||||
|     )) | ||||
| } | ||||
|  | ||||
| @ -202,6 +203,7 @@ pub async fn update_connector_mandate_id( | ||||
| pub async fn get_customer_mandates( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     req: customers::CustomerId, | ||||
| ) -> RouterResponse<Vec<mandates::MandateResponse>> { | ||||
|     let mandates = state | ||||
| @ -221,7 +223,10 @@ pub async fn get_customer_mandates( | ||||
|     } else { | ||||
|         let mut response_vec = Vec::with_capacity(mandates.len()); | ||||
|         for mandate in mandates { | ||||
|             response_vec.push(mandates::MandateResponse::from_db_mandate(&state, mandate).await?); | ||||
|             response_vec.push( | ||||
|                 mandates::MandateResponse::from_db_mandate(&state, key_store.clone(), mandate) | ||||
|                     .await?, | ||||
|             ); | ||||
|         } | ||||
|         Ok(services::ApplicationResponse::Json(response_vec)) | ||||
|     } | ||||
| @ -383,6 +388,7 @@ where | ||||
| pub async fn retrieve_mandates_list( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     constraints: api_models::mandates::MandateListConstraints, | ||||
| ) -> RouterResponse<Vec<api_models::mandates::MandateResponse>> { | ||||
|     let mandates = state | ||||
| @ -392,11 +398,9 @@ pub async fn retrieve_mandates_list( | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Unable to retrieve mandates")?; | ||||
|     let mandates_list = future::try_join_all( | ||||
|         mandates | ||||
|             .into_iter() | ||||
|             .map(|mandate| mandates::MandateResponse::from_db_mandate(&state, mandate)), | ||||
|     ) | ||||
|     let mandates_list = future::try_join_all(mandates.into_iter().map(|mandate| { | ||||
|         mandates::MandateResponse::from_db_mandate(&state, key_store.clone(), mandate) | ||||
|     })) | ||||
|     .await?; | ||||
|     Ok(services::ApplicationResponse::Json(mandates_list)) | ||||
| } | ||||
|  | ||||
| @ -558,6 +558,7 @@ pub async fn add_card_hs( | ||||
|         req, | ||||
|         &merchant_account.merchant_id, | ||||
|     ); | ||||
|  | ||||
|     Ok(( | ||||
|         payment_method_resp, | ||||
|         store_card_payload.duplicate.unwrap_or(false), | ||||
| @ -2508,11 +2509,19 @@ pub async fn list_customer_payment_method( | ||||
|         let parent_payment_method_token = generate_id(consts::ID_LENGTH, "token"); | ||||
|  | ||||
|         let (card, pmd, hyperswitch_token_data) = match pm.payment_method { | ||||
|             enums::PaymentMethod::Card => ( | ||||
|                 Some(get_card_details(&pm, key, state).await?), | ||||
|                 None, | ||||
|                 PaymentTokenData::permanent_card(pm.payment_method_id.clone()), | ||||
|             ), | ||||
|             enums::PaymentMethod::Card => { | ||||
|                 let card_details = get_card_details_with_locker_fallback(&pm, key, state).await?; | ||||
|  | ||||
|                 if card_details.is_some() { | ||||
|                     ( | ||||
|                         card_details, | ||||
|                         None, | ||||
|                         PaymentTokenData::permanent_card(pm.payment_method_id.clone()), | ||||
|                     ) | ||||
|                 } else { | ||||
|                     continue; | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             #[cfg(feature = "payouts")] | ||||
|             enums::PaymentMethod::BankTransfer => { | ||||
| @ -2571,6 +2580,7 @@ pub async fn list_customer_payment_method( | ||||
|         }; | ||||
|  | ||||
|         //Need validation for enabled payment method ,querying MCA | ||||
|  | ||||
|         let pma = api::CustomerPaymentMethod { | ||||
|             payment_token: parent_payment_method_token.to_owned(), | ||||
|             customer_id: pm.customer_id, | ||||
| @ -2700,7 +2710,38 @@ pub async fn list_customer_payment_method( | ||||
|     Ok(services::ApplicationResponse::Json(response)) | ||||
| } | ||||
|  | ||||
| async fn get_card_details( | ||||
| pub async fn get_card_details_with_locker_fallback( | ||||
|     pm: &payment_method::PaymentMethod, | ||||
|     key: &[u8], | ||||
|     state: &routes::AppState, | ||||
| ) -> errors::RouterResult<Option<api::CardDetailFromLocker>> { | ||||
|     let card_decrypted = | ||||
|         decrypt::<serde_json::Value, masking::WithType>(pm.payment_method_data.clone(), key) | ||||
|             .await | ||||
|             .change_context(errors::StorageError::DecryptionError) | ||||
|             .attach_printable("unable to decrypt card details") | ||||
|             .ok() | ||||
|             .flatten() | ||||
|             .map(|x| x.into_inner().expose()) | ||||
|             .and_then(|v| serde_json::from_value::<PaymentMethodsData>(v).ok()) | ||||
|             .and_then(|pmd| match pmd { | ||||
|                 PaymentMethodsData::Card(crd) => Some(api::CardDetailFromLocker::from(crd)), | ||||
|                 _ => None, | ||||
|             }); | ||||
|  | ||||
|     Ok(if let Some(mut crd) = card_decrypted { | ||||
|         if crd.saved_to_locker { | ||||
|             crd.scheme = pm.scheme.clone(); | ||||
|             Some(crd) | ||||
|         } else { | ||||
|             None | ||||
|         } | ||||
|     } else { | ||||
|         Some(get_card_details_from_locker(state, pm).await?) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| pub async fn get_card_details_without_locker_fallback( | ||||
|     pm: &payment_method::PaymentMethod, | ||||
|     key: &[u8], | ||||
|     state: &routes::AppState, | ||||
| @ -2979,25 +3020,32 @@ impl TempLockerCardSupport { | ||||
| pub async fn retrieve_payment_method( | ||||
|     state: routes::AppState, | ||||
|     pm: api::PaymentMethodId, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
| ) -> errors::RouterResponse<api::PaymentMethodResponse> { | ||||
|     let db = state.store.as_ref(); | ||||
|     let pm = db | ||||
|         .find_payment_method(&pm.payment_method_id) | ||||
|         .await | ||||
|         .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; | ||||
|  | ||||
|     let key = key_store.key.peek(); | ||||
|     let card = if pm.payment_method == enums::PaymentMethod::Card { | ||||
|         let card = get_card_from_locker( | ||||
|             &state, | ||||
|             &pm.customer_id, | ||||
|             &pm.merchant_id, | ||||
|             &pm.payment_method_id, | ||||
|         ) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|         .attach_printable("Error getting card from card vault")?; | ||||
|         let card_detail = payment_methods::get_card_detail(&pm, card) | ||||
|         let card_detail = if state.conf.locker.locker_enabled { | ||||
|             let card = get_card_from_locker( | ||||
|                 &state, | ||||
|                 &pm.customer_id, | ||||
|                 &pm.merchant_id, | ||||
|                 &pm.payment_method_id, | ||||
|             ) | ||||
|             .await | ||||
|             .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|             .attach_printable("Failed while getting card details from locker")?; | ||||
|             .attach_printable("Error getting card from card vault")?; | ||||
|             payment_methods::get_card_detail(&pm, card) | ||||
|                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                 .attach_printable("Failed while getting card details from locker")? | ||||
|         } else { | ||||
|             get_card_details_without_locker_fallback(&pm, key, &state).await? | ||||
|         }; | ||||
|         Some(card_detail) | ||||
|     } else { | ||||
|         None | ||||
|  | ||||
| @ -352,18 +352,26 @@ pub fn mk_add_card_response_hs( | ||||
|     req: api::PaymentMethodCreate, | ||||
|     merchant_id: &str, | ||||
| ) -> api::PaymentMethodResponse { | ||||
|     let mut card_number = card.card_number.peek().to_owned(); | ||||
|     let card_number = card.card_number.clone(); | ||||
|     let last4_digits = card_number.clone().get_last4(); | ||||
|     let card_isin = card_number.get_card_isin(); | ||||
|  | ||||
|     let card = api::CardDetailFromLocker { | ||||
|         scheme: None, | ||||
|         last4_digits: Some(card_number.split_off(card_number.len() - 4)), | ||||
|         issuer_country: None, // [#256] bin mapping | ||||
|         card_number: Some(card.card_number), | ||||
|         expiry_month: Some(card.card_exp_month), | ||||
|         expiry_year: Some(card.card_exp_year), | ||||
|         card_token: None,       // [#256] | ||||
|         card_fingerprint: None, // fingerprint not send by basilisk-hs need to have this feature in case we need it in future | ||||
|         card_holder_name: card.card_holder_name, | ||||
|         nick_name: card.nick_name, | ||||
|         last4_digits: Some(last4_digits), | ||||
|         issuer_country: None, | ||||
|         card_number: Some(card.card_number.clone()), | ||||
|         expiry_month: Some(card.card_exp_month.clone()), | ||||
|         expiry_year: Some(card.card_exp_year.clone()), | ||||
|         card_token: None, | ||||
|         card_fingerprint: None, | ||||
|         card_holder_name: card.card_holder_name.clone(), | ||||
|         nick_name: card.nick_name.clone(), | ||||
|         card_isin: Some(card_isin), | ||||
|         card_issuer: card.card_issuer, | ||||
|         card_network: card.card_network, | ||||
|         card_type: card.card_type, | ||||
|         saved_to_locker: true, | ||||
|     }; | ||||
|     api::PaymentMethodResponse { | ||||
|         merchant_id: merchant_id.to_owned(), | ||||
| @ -399,6 +407,11 @@ pub fn mk_add_card_response( | ||||
|         card_fingerprint: Some(response.card_fingerprint), | ||||
|         card_holder_name: card.card_holder_name, | ||||
|         nick_name: card.nick_name, | ||||
|         card_isin: None, | ||||
|         card_issuer: None, | ||||
|         card_network: None, | ||||
|         card_type: None, | ||||
|         saved_to_locker: true, | ||||
|     }; | ||||
|     api::PaymentMethodResponse { | ||||
|         merchant_id: merchant_id.to_owned(), | ||||
| @ -597,6 +610,8 @@ pub fn get_card_detail( | ||||
| ) -> CustomResult<api::CardDetailFromLocker, errors::VaultError> { | ||||
|     let card_number = response.card_number; | ||||
|     let mut last4_digits = card_number.peek().to_owned(); | ||||
|     //fetch form card bin | ||||
|  | ||||
|     let card_detail = api::CardDetailFromLocker { | ||||
|         scheme: pm.scheme.to_owned(), | ||||
|         issuer_country: pm.issuer_country.clone(), | ||||
| @ -608,6 +623,11 @@ pub fn get_card_detail( | ||||
|         card_fingerprint: None, | ||||
|         card_holder_name: response.name_on_card, | ||||
|         nick_name: response.nick_name.map(masking::Secret::new), | ||||
|         card_isin: None, | ||||
|         card_issuer: None, | ||||
|         card_network: None, | ||||
|         card_type: None, | ||||
|         saved_to_locker: true, | ||||
|     }; | ||||
|     Ok(card_detail) | ||||
| } | ||||
|  | ||||
| @ -92,7 +92,9 @@ impl Feature<api::Authorize, types::PaymentsAuthorizeData> for types::PaymentsAu | ||||
|  | ||||
|             metrics::PAYMENT_COUNT.add(&metrics::CONTEXT, 1, &[]); // Metrics | ||||
|  | ||||
|             if resp.request.setup_mandate_details.clone().is_some() { | ||||
|             let is_mandate = resp.request.setup_mandate_details.is_some(); | ||||
|  | ||||
|             if is_mandate { | ||||
|                 let payment_method_id = Box::pin(tokenization::save_payment_method( | ||||
|                     state, | ||||
|                     connector, | ||||
|  | ||||
| @ -208,6 +208,7 @@ impl types::SetupMandateRouterData { | ||||
|                 .to_setup_mandate_failed_response()?; | ||||
|  | ||||
|                 let payment_method_type = self.request.payment_method_type; | ||||
|  | ||||
|                 let pm_id = Box::pin(tokenization::save_payment_method( | ||||
|                     state, | ||||
|                     connector, | ||||
|  | ||||
| @ -509,16 +509,23 @@ pub async fn get_token_for_recurring_mandate( | ||||
|     }; | ||||
|  | ||||
|     if let diesel_models::enums::PaymentMethod::Card = payment_method.payment_method { | ||||
|         let _ = | ||||
|             cards::get_lookup_key_from_locker(state, &token, &payment_method, merchant_key_store) | ||||
|                 .await?; | ||||
|         if state.conf.locker.locker_enabled { | ||||
|             let _ = cards::get_lookup_key_from_locker( | ||||
|                 state, | ||||
|                 &token, | ||||
|                 &payment_method, | ||||
|                 merchant_key_store, | ||||
|             ) | ||||
|             .await?; | ||||
|         } | ||||
|  | ||||
|         if let Some(payment_method_from_request) = req.payment_method { | ||||
|             let pm: storage_enums::PaymentMethod = payment_method_from_request; | ||||
|             if pm != payment_method.payment_method { | ||||
|                 Err(report!(errors::ApiErrorResponse::PreconditionFailed { | ||||
|                     message: | ||||
|                         "payment method in request does not match previously provided payment \ | ||||
|                                   method information" | ||||
|                         method information" | ||||
|                             .into() | ||||
|                 }))? | ||||
|             } | ||||
| @ -971,7 +978,6 @@ pub fn payment_intent_status_fsm( | ||||
|         None => storage_enums::IntentStatus::RequiresPaymentMethod, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn add_domain_task_to_pt<Op>( | ||||
|     operation: &Op, | ||||
|     state: &AppState, | ||||
| @ -1050,6 +1056,10 @@ pub(crate) async fn get_payment_method_create_request( | ||||
|                         card_exp_year: card.card_exp_year.clone(), | ||||
|                         card_holder_name: card.card_holder_name.clone(), | ||||
|                         nick_name: card.nick_name.clone(), | ||||
|                         card_issuing_country: card.card_issuing_country.clone(), | ||||
|                         card_network: card.card_network.clone(), | ||||
|                         card_issuer: card.card_issuer.clone(), | ||||
|                         card_type: card.card_type.clone(), | ||||
|                     }; | ||||
|                     let customer_id = customer.customer_id.clone(); | ||||
|                     let payment_method_request = api::PaymentMethodCreate { | ||||
| @ -3359,21 +3369,23 @@ pub async fn get_additional_payment_data( | ||||
|                             }, | ||||
|                         )) | ||||
|                     }); | ||||
|                 card_info.unwrap_or(api_models::payments::AdditionalPaymentData::Card(Box::new( | ||||
|                     api_models::payments::AdditionalCardInfo { | ||||
|                         card_issuer: None, | ||||
|                         card_network: None, | ||||
|                         bank_code: None, | ||||
|                         card_type: None, | ||||
|                         card_issuing_country: None, | ||||
|                         last4, | ||||
|                         card_isin, | ||||
|                         card_extended_bin, | ||||
|                         card_exp_month: Some(card_data.card_exp_month.clone()), | ||||
|                         card_exp_year: Some(card_data.card_exp_year.clone()), | ||||
|                         card_holder_name: card_data.card_holder_name.clone(), | ||||
|                     }, | ||||
|                 ))) | ||||
|                 card_info.unwrap_or_else(|| { | ||||
|                     api_models::payments::AdditionalPaymentData::Card(Box::new( | ||||
|                         api_models::payments::AdditionalCardInfo { | ||||
|                             card_issuer: None, | ||||
|                             card_network: None, | ||||
|                             bank_code: None, | ||||
|                             card_type: None, | ||||
|                             card_issuing_country: None, | ||||
|                             last4, | ||||
|                             card_isin, | ||||
|                             card_extended_bin, | ||||
|                             card_exp_month: Some(card_data.card_exp_month.clone()), | ||||
|                             card_exp_year: Some(card_data.card_exp_year.clone()), | ||||
|                             card_holder_name: card_data.card_holder_name.clone(), | ||||
|                         }, | ||||
|                     )) | ||||
|                 }) | ||||
|             } | ||||
|         } | ||||
|         api_models::payments::PaymentMethodData::BankRedirect(bank_redirect_data) => { | ||||
|  | ||||
| @ -14,7 +14,7 @@ use crate::{ | ||||
|     services, | ||||
|     types::{ | ||||
|         self, | ||||
|         api::{self, CardDetailsPaymentMethod, PaymentMethodCreateExt}, | ||||
|         api::{self, CardDetailFromLocker, CardDetailsPaymentMethod, PaymentMethodCreateExt}, | ||||
|         domain, | ||||
|         storage::enums as storage_enums, | ||||
|     }, | ||||
| @ -74,12 +74,21 @@ where | ||||
|                 .await?; | ||||
|                 let merchant_id = &merchant_account.merchant_id; | ||||
|  | ||||
|                 let locker_response = save_in_locker( | ||||
|                     state, | ||||
|                     merchant_account, | ||||
|                     payment_method_create_request.to_owned(), | ||||
|                 ) | ||||
|                 .await?; | ||||
|                 let locker_response = if !state.conf.locker.locker_enabled { | ||||
|                     skip_saving_card_in_locker( | ||||
|                         merchant_account, | ||||
|                         payment_method_create_request.to_owned(), | ||||
|                     ) | ||||
|                     .await? | ||||
|                 } else { | ||||
|                     save_in_locker( | ||||
|                         state, | ||||
|                         merchant_account, | ||||
|                         payment_method_create_request.to_owned(), | ||||
|                     ) | ||||
|                     .await? | ||||
|                 }; | ||||
|  | ||||
|                 let is_duplicate = locker_response.1; | ||||
|  | ||||
|                 let pm_card_details = locker_response.0.card.as_ref().map(|card| { | ||||
| @ -168,6 +177,85 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| async fn skip_saving_card_in_locker( | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|     payment_method_request: api::PaymentMethodCreate, | ||||
| ) -> RouterResult<(api_models::payment_methods::PaymentMethodResponse, bool)> { | ||||
|     let merchant_id = &merchant_account.merchant_id; | ||||
|     let customer_id = payment_method_request | ||||
|         .clone() | ||||
|         .customer_id | ||||
|         .clone() | ||||
|         .get_required_value("customer_id")?; | ||||
|     let payment_method_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm"); | ||||
|  | ||||
|     let last4_digits = payment_method_request | ||||
|         .card | ||||
|         .clone() | ||||
|         .map(|c| c.card_number.get_last4()); | ||||
|  | ||||
|     let card_isin = payment_method_request | ||||
|         .card | ||||
|         .clone() | ||||
|         .map(|c: api_models::payment_methods::CardDetail| c.card_number.get_card_isin()); | ||||
|  | ||||
|     match payment_method_request.card.clone() { | ||||
|         Some(card) => { | ||||
|             let card_detail = CardDetailFromLocker { | ||||
|                 scheme: None, | ||||
|                 issuer_country: card.card_issuing_country.clone(), | ||||
|                 last4_digits: last4_digits.clone(), | ||||
|                 card_number: None, | ||||
|                 expiry_month: Some(card.card_exp_month.clone()), | ||||
|                 expiry_year: Some(card.card_exp_year), | ||||
|                 card_token: None, | ||||
|                 card_holder_name: card.card_holder_name.clone(), | ||||
|                 card_fingerprint: None, | ||||
|                 nick_name: None, | ||||
|                 card_isin: card_isin.clone(), | ||||
|                 card_issuer: card.card_issuer.clone(), | ||||
|                 card_network: card.card_network.clone(), | ||||
|                 card_type: card.card_type.clone(), | ||||
|                 saved_to_locker: false, | ||||
|             }; | ||||
|             let pm_resp = api::PaymentMethodResponse { | ||||
|                 merchant_id: merchant_id.to_string(), | ||||
|                 customer_id: Some(customer_id), | ||||
|                 payment_method_id, | ||||
|                 payment_method: payment_method_request.payment_method, | ||||
|                 payment_method_type: payment_method_request.payment_method_type, | ||||
|                 card: Some(card_detail), | ||||
|                 recurring_enabled: false, | ||||
|                 installment_payment_enabled: false, | ||||
|                 payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), | ||||
|                 metadata: None, | ||||
|                 created: Some(common_utils::date_time::now()), | ||||
|                 bank_transfer: None, | ||||
|             }; | ||||
|  | ||||
|             Ok((pm_resp, false)) | ||||
|         } | ||||
|         None => { | ||||
|             let pm_id = common_utils::generate_id(crate::consts::ID_LENGTH, "pm"); | ||||
|             let payment_method_response = api::PaymentMethodResponse { | ||||
|                 merchant_id: merchant_id.to_string(), | ||||
|                 customer_id: Some(customer_id), | ||||
|                 payment_method_id: pm_id, | ||||
|                 payment_method: payment_method_request.payment_method, | ||||
|                 payment_method_type: payment_method_request.payment_method_type, | ||||
|                 card: None, | ||||
|                 metadata: None, | ||||
|                 created: Some(common_utils::date_time::now()), | ||||
|                 recurring_enabled: false, | ||||
|                 installment_payment_enabled: false, | ||||
|                 payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), | ||||
|                 bank_transfer: None, | ||||
|             }; | ||||
|             Ok((payment_method_response, false)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub async fn save_in_locker( | ||||
|     state: &AppState, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
|  | ||||
| @ -1,6 +1,6 @@ | ||||
| use common_utils::{ | ||||
|     errors::CustomResult, | ||||
|     ext_traits::{StringExt, ValueExt}, | ||||
|     ext_traits::{AsyncExt, StringExt, ValueExt}, | ||||
| }; | ||||
| use diesel_models::encryption::Encryption; | ||||
| use error_stack::{IntoReport, ResultExt}; | ||||
| @ -19,6 +19,7 @@ use crate::{ | ||||
|     }, | ||||
|     db::StorageInterface, | ||||
|     routes::AppState, | ||||
|     services, | ||||
|     types::{ | ||||
|         api::{self, enums as api_enums}, | ||||
|         domain::{ | ||||
| @ -184,6 +185,10 @@ pub async fn save_payout_data_to_locker( | ||||
|                 card_exp_month: card.expiry_month.to_owned(), | ||||
|                 card_exp_year: card.expiry_year.to_owned(), | ||||
|                 nick_name: None, | ||||
|                 card_issuing_country: None, | ||||
|                 card_network: None, | ||||
|                 card_issuer: None, | ||||
|                 card_type: None, | ||||
|             }; | ||||
|             let payload = StoreLockerReq::LockerCard(StoreCardReq { | ||||
|                 merchant_id: &merchant_account.merchant_id, | ||||
| @ -267,20 +272,65 @@ pub async fn save_payout_data_to_locker( | ||||
|     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|     .attach_printable("Error updating payouts in saved payout method")?; | ||||
|  | ||||
|     let pm_data = api::payment_methods::PaymentMethodsData::Card( | ||||
|         api::payment_methods::CardDetailsPaymentMethod { | ||||
|             last4_digits: card_details | ||||
|                 .as_ref() | ||||
|                 .map(|c| c.card_number.clone().get_last4()), | ||||
|             issuer_country: None, | ||||
|             expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), | ||||
|             expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), | ||||
|             nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), | ||||
|             card_holder_name: card_details | ||||
|                 .as_ref() | ||||
|                 .and_then(|c| c.card_holder_name.clone()), | ||||
|         }, | ||||
|     ); | ||||
|     // fetch card info from db | ||||
|     let card_isin = card_details | ||||
|         .as_ref() | ||||
|         .map(|c| c.card_number.clone().get_card_isin()); | ||||
|  | ||||
|     let pm_data = card_isin | ||||
|         .clone() | ||||
|         .async_and_then(|card_isin| async move { | ||||
|             db.get_card_info(&card_isin) | ||||
|                 .await | ||||
|                 .map_err(|error| services::logger::warn!(card_info_error=?error)) | ||||
|                 .ok() | ||||
|         }) | ||||
|         .await | ||||
|         .flatten() | ||||
|         .map(|card_info| { | ||||
|             api::payment_methods::PaymentMethodsData::Card( | ||||
|                 api::payment_methods::CardDetailsPaymentMethod { | ||||
|                     last4_digits: card_details | ||||
|                         .as_ref() | ||||
|                         .map(|c| c.card_number.clone().get_last4()), | ||||
|                     issuer_country: card_info.card_issuing_country, | ||||
|                     expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), | ||||
|                     expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), | ||||
|                     nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), | ||||
|                     card_holder_name: card_details | ||||
|                         .as_ref() | ||||
|                         .and_then(|c| c.card_holder_name.clone()), | ||||
|  | ||||
|                     card_isin: card_isin.clone(), | ||||
|                     card_issuer: card_info.card_issuer, | ||||
|                     card_network: card_info.card_network, | ||||
|                     card_type: card_info.card_type, | ||||
|                     saved_to_locker: true, | ||||
|                 }, | ||||
|             ) | ||||
|         }) | ||||
|         .unwrap_or_else(|| { | ||||
|             api::payment_methods::PaymentMethodsData::Card( | ||||
|                 api::payment_methods::CardDetailsPaymentMethod { | ||||
|                     last4_digits: card_details | ||||
|                         .as_ref() | ||||
|                         .map(|c| c.card_number.clone().get_last4()), | ||||
|                     issuer_country: None, | ||||
|                     expiry_month: card_details.as_ref().map(|c| c.card_exp_month.clone()), | ||||
|                     expiry_year: card_details.as_ref().map(|c| c.card_exp_year.clone()), | ||||
|                     nick_name: card_details.as_ref().and_then(|c| c.nick_name.clone()), | ||||
|                     card_holder_name: card_details | ||||
|                         .as_ref() | ||||
|                         .and_then(|c| c.card_holder_name.clone()), | ||||
|  | ||||
|                     card_isin: card_isin.clone(), | ||||
|                     card_issuer: None, | ||||
|                     card_network: None, | ||||
|                     card_type: None, | ||||
|                     saved_to_locker: true, | ||||
|                 }, | ||||
|             ) | ||||
|         }); | ||||
|  | ||||
|     let card_details_encrypted = | ||||
|         cards::create_encrypted_payment_method_data(key_store, Some(pm_data)).await; | ||||
|  | ||||
| @ -421,6 +421,7 @@ pub async fn mandates_incoming_webhook_flow<W: types::OutgoingWebhookType>( | ||||
|     state: AppState, | ||||
|     merchant_account: domain::MerchantAccount, | ||||
|     business_profile: diesel_models::business_profile::BusinessProfile, | ||||
|     key_store: domain::MerchantKeyStore, | ||||
|     webhook_details: api::IncomingWebhookDetails, | ||||
|     source_verified: bool, | ||||
|     event_type: api_models::webhooks::IncomingWebhookEvent, | ||||
| @ -464,8 +465,12 @@ pub async fn mandates_incoming_webhook_flow<W: types::OutgoingWebhookType>( | ||||
|             .await | ||||
|             .to_not_found_response(errors::ApiErrorResponse::MandateNotFound)?; | ||||
|         let mandates_response = Box::new( | ||||
|             api::mandates::MandateResponse::from_db_mandate(&state, updated_mandate.clone()) | ||||
|                 .await?, | ||||
|             api::mandates::MandateResponse::from_db_mandate( | ||||
|                 &state, | ||||
|                 key_store, | ||||
|                 updated_mandate.clone(), | ||||
|             ) | ||||
|             .await?, | ||||
|         ); | ||||
|         let event_type: Option<enums::EventType> = updated_mandate.mandate_status.foreign_into(); | ||||
|         if let Some(outgoing_event_type) = event_type { | ||||
| @ -1237,6 +1242,7 @@ pub async fn webhooks_core<W: types::OutgoingWebhookType, Ctx: PaymentMethodRetr | ||||
|                 state.clone(), | ||||
|                 merchant_account, | ||||
|                 business_profile, | ||||
|                 key_store, | ||||
|                 webhook_details, | ||||
|                 source_verified, | ||||
|                 event_type, | ||||
|  | ||||
| @ -226,7 +226,12 @@ pub async fn get_customer_mandates( | ||||
|         &req, | ||||
|         customer_id, | ||||
|         |state, auth, req| { | ||||
|             crate::core::mandate::get_customer_mandates(state, auth.merchant_account, req) | ||||
|             crate::core::mandate::get_customer_mandates( | ||||
|                 state, | ||||
|                 auth.merchant_account, | ||||
|                 auth.key_store, | ||||
|                 req, | ||||
|             ) | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|             &auth::ApiKeyAuth, | ||||
|  | ||||
| @ -41,7 +41,7 @@ pub async fn get_mandate( | ||||
|         state, | ||||
|         &req, | ||||
|         mandate_id, | ||||
|         |state, auth, req| mandate::get_mandate(state, auth.merchant_account, req), | ||||
|         |state, auth, req| mandate::get_mandate(state, auth.merchant_account, auth.key_store, req), | ||||
|         &auth::ApiKeyAuth, | ||||
|         api_locking::LockAction::NotApplicable, | ||||
|     ) | ||||
| @ -123,7 +123,9 @@ pub async fn retrieve_mandates_list( | ||||
|         state, | ||||
|         &req, | ||||
|         payload, | ||||
|         |state, auth, req| mandate::retrieve_mandates_list(state, auth.merchant_account, req), | ||||
|         |state, auth, req| { | ||||
|             mandate::retrieve_mandates_list(state, auth.merchant_account, auth.key_store, req) | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|             &auth::ApiKeyAuth, | ||||
|             &auth::JWTAuth(Permission::MandateRead), | ||||
|  | ||||
| @ -242,7 +242,7 @@ pub async fn payment_method_retrieve_api( | ||||
|         state, | ||||
|         &req, | ||||
|         payload, | ||||
|         |state, _auth, pm| cards::retrieve_payment_method(state, pm), | ||||
|         |state, auth, pm| cards::retrieve_payment_method(state, pm, auth.key_store), | ||||
|         &auth::ApiKeyAuth, | ||||
|         api_locking::LockAction::NotApplicable, | ||||
|     )) | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| use api_models::mandates; | ||||
| pub use api_models::mandates::{MandateId, MandateResponse, MandateRevokedResponse}; | ||||
| use error_stack::ResultExt; | ||||
| use masking::PeekInterface; | ||||
| use serde::{Deserialize, Serialize}; | ||||
|  | ||||
| use crate::{ | ||||
| @ -11,7 +12,7 @@ use crate::{ | ||||
|     newtype, | ||||
|     routes::AppState, | ||||
|     types::{ | ||||
|         api, | ||||
|         api, domain, | ||||
|         storage::{self, enums as storage_enums}, | ||||
|     }, | ||||
| }; | ||||
| @ -23,12 +24,20 @@ newtype!( | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| pub(crate) trait MandateResponseExt: Sized { | ||||
|     async fn from_db_mandate(state: &AppState, mandate: storage::Mandate) -> RouterResult<Self>; | ||||
|     async fn from_db_mandate( | ||||
|         state: &AppState, | ||||
|         key_store: domain::MerchantKeyStore, | ||||
|         mandate: storage::Mandate, | ||||
|     ) -> RouterResult<Self>; | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl MandateResponseExt for MandateResponse { | ||||
|     async fn from_db_mandate(state: &AppState, mandate: storage::Mandate) -> RouterResult<Self> { | ||||
|     async fn from_db_mandate( | ||||
|         state: &AppState, | ||||
|         key_store: domain::MerchantKeyStore, | ||||
|         mandate: storage::Mandate, | ||||
|     ) -> RouterResult<Self> { | ||||
|         let db = &*state.store; | ||||
|         let payment_method = db | ||||
|             .find_payment_method(&mandate.payment_method_id) | ||||
| @ -36,21 +45,35 @@ impl MandateResponseExt for MandateResponse { | ||||
|             .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; | ||||
|  | ||||
|         let card = if payment_method.payment_method == storage_enums::PaymentMethod::Card { | ||||
|             let card = payment_methods::cards::get_card_from_locker( | ||||
|                 state, | ||||
|                 &payment_method.customer_id, | ||||
|                 &payment_method.merchant_id, | ||||
|                 &payment_method.payment_method_id, | ||||
|             ) | ||||
|             .await?; | ||||
|             let card_detail = payment_methods::transformers::get_card_detail(&payment_method, card) | ||||
|                 .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                 .attach_printable("Failed while getting card details")?; | ||||
|             Some(MandateCardDetails::from(card_detail).into_inner()) | ||||
|             // if locker is disabled , decrypt the payment method data | ||||
|             let card_details = if state.conf.locker.locker_enabled { | ||||
|                 let card = payment_methods::cards::get_card_from_locker( | ||||
|                     state, | ||||
|                     &payment_method.customer_id, | ||||
|                     &payment_method.merchant_id, | ||||
|                     &payment_method.payment_method_id, | ||||
|                 ) | ||||
|                 .await?; | ||||
|  | ||||
|                 payment_methods::transformers::get_card_detail(&payment_method, card) | ||||
|                     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                     .attach_printable("Failed while getting card details")? | ||||
|             } else { | ||||
|                 payment_methods::cards::get_card_details_without_locker_fallback( | ||||
|                     &payment_method, | ||||
|                     key_store.key.get_inner().peek(), | ||||
|                     state, | ||||
|                 ) | ||||
|                 .await? | ||||
|             }; | ||||
|  | ||||
|             Some(MandateCardDetails::from(card_details).into_inner()) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
|  | ||||
|         let payment_method_type = payment_method | ||||
|             .payment_method_type | ||||
|             .map(|pmt| pmt.to_string()); | ||||
|         Ok(Self { | ||||
|             mandate_id: mandate.mandate_id, | ||||
|             customer_acceptance: Some(api::payments::CustomerAcceptance { | ||||
| @ -68,6 +91,7 @@ impl MandateResponseExt for MandateResponse { | ||||
|             card, | ||||
|             status: mandate.mandate_status, | ||||
|             payment_method: payment_method.payment_method.to_string(), | ||||
|             payment_method_type, | ||||
|             payment_method_id: mandate.payment_method_id, | ||||
|         }) | ||||
|     } | ||||
| @ -84,6 +108,10 @@ impl From<api::payment_methods::CardDetailFromLocker> for MandateCardDetails { | ||||
|             scheme: card_details_from_locker.scheme, | ||||
|             issuer_country: card_details_from_locker.issuer_country, | ||||
|             card_fingerprint: card_details_from_locker.card_fingerprint, | ||||
|             card_isin: card_details_from_locker.card_isin, | ||||
|             card_issuer: card_details_from_locker.card_issuer, | ||||
|             card_network: card_details_from_locker.card_network, | ||||
|             card_type: card_details_from_locker.card_type, | ||||
|         } | ||||
|         .into() | ||||
|     } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Amisha Prabhat
					Amisha Prabhat