mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 10:06:32 +08:00 
			
		
		
		
	feat(payment_methods): Filter payment methods based on pm client secret (#4249)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -613,3 +613,6 @@ payment_attempts = "hyperswitch-payment-attempt-events" | ||||
| payment_intents = "hyperswitch-payment-intent-events" | ||||
| refunds = "hyperswitch-refund-events" | ||||
| disputes = "hyperswitch-dispute-events" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
| @ -319,3 +319,6 @@ connectors_with_webhook_source_verification_call = "paypal"         # List of co | ||||
|  | ||||
| [unmasked_headers] | ||||
| keys = "user-agent" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
| @ -330,3 +330,6 @@ connectors_with_webhook_source_verification_call = "paypal"     # List of connec | ||||
|  | ||||
| [unmasked_headers] | ||||
| keys = "user-agent" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
| @ -334,3 +334,6 @@ connectors_with_webhook_source_verification_call = "paypal"        # List of con | ||||
|  | ||||
| [unmasked_headers] | ||||
| keys = "user-agent" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
| @ -615,3 +615,6 @@ payment_attempts = "hyperswitch-payment-attempt-events" | ||||
| payment_intents = "hyperswitch-payment-intent-events" | ||||
| refunds = "hyperswitch-refund-events" | ||||
| disputes = "hyperswitch-dispute-events" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
|  | ||||
| @ -474,3 +474,6 @@ payment_attempts = "hyperswitch-payment-attempt-events" | ||||
| payment_intents = "hyperswitch-payment-intent-events" | ||||
| refunds = "hyperswitch-refund-events" | ||||
| disputes = "hyperswitch-dispute-events" | ||||
|  | ||||
| [saved_payment_methods] | ||||
| sdk_eligible_payment_methods = ["card"] | ||||
| @ -143,6 +143,8 @@ pub enum PaymentMethodUpdate { | ||||
|         status: Option<storage_enums::PaymentMethodStatus>, | ||||
|         locker_id: Option<String>, | ||||
|         payment_method: Option<storage_enums::PaymentMethod>, | ||||
|         payment_method_type: Option<storage_enums::PaymentMethodType>, | ||||
|         payment_method_issuer: Option<String>, | ||||
|     }, | ||||
|     ConnectorMandateDetailsUpdate { | ||||
|         connector_mandate_details: Option<serde_json::Value>, | ||||
| @ -162,6 +164,8 @@ pub struct PaymentMethodUpdateInternal { | ||||
|     locker_id: Option<String>, | ||||
|     payment_method: Option<storage_enums::PaymentMethod>, | ||||
|     connector_mandate_details: Option<serde_json::Value>, | ||||
|     payment_method_type: Option<storage_enums::PaymentMethodType>, | ||||
|     payment_method_issuer: Option<String>, | ||||
| } | ||||
|  | ||||
| impl PaymentMethodUpdateInternal { | ||||
| @ -208,6 +212,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id: None, | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|             PaymentMethodUpdate::PaymentMethodDataUpdate { | ||||
|                 payment_method_data, | ||||
| @ -220,6 +226,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id: None, | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|             PaymentMethodUpdate::LastUsedUpdate { last_used_at } => Self { | ||||
|                 metadata: None, | ||||
| @ -230,6 +238,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id: None, | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|             PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate { | ||||
|                 network_transaction_id, | ||||
| @ -243,6 +253,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id: None, | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|             PaymentMethodUpdate::StatusUpdate { status } => Self { | ||||
|                 metadata: None, | ||||
| @ -253,12 +265,16 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id: None, | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|             PaymentMethodUpdate::AdditionalDataUpdate { | ||||
|                 payment_method_data, | ||||
|                 status, | ||||
|                 locker_id, | ||||
|                 payment_method, | ||||
|                 payment_method_type, | ||||
|                 payment_method_issuer, | ||||
|             } => Self { | ||||
|                 metadata: None, | ||||
|                 payment_method_data, | ||||
| @ -268,6 +284,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 locker_id, | ||||
|                 payment_method, | ||||
|                 connector_mandate_details: None, | ||||
|                 payment_method_issuer, | ||||
|                 payment_method_type, | ||||
|             }, | ||||
|             PaymentMethodUpdate::ConnectorMandateDetailsUpdate { | ||||
|                 connector_mandate_details, | ||||
| @ -280,6 +298,8 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal { | ||||
|                 payment_method: None, | ||||
|                 connector_mandate_details, | ||||
|                 network_transaction_id: None, | ||||
|                 payment_method_issuer: None, | ||||
|                 payment_method_type: None, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -365,5 +365,6 @@ pub(crate) async fn fetch_raw_secrets( | ||||
|         connector_onboarding, | ||||
|         cors: conf.cors, | ||||
|         unmasked_headers: conf.unmasked_headers, | ||||
|         saved_payment_methods: conf.saved_payment_methods, | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -119,6 +119,7 @@ pub struct Settings<S: SecretState> { | ||||
|     #[cfg(feature = "olap")] | ||||
|     pub connector_onboarding: SecretStateContainer<ConnectorOnboarding, S>, | ||||
|     pub unmasked_headers: UnmaskedHeaders, | ||||
|     pub saved_payment_methods: EligiblePaymentMethods, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| @ -165,6 +166,11 @@ pub struct PaymentMethodAuth { | ||||
|     pub pm_auth_key: Secret<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct EligiblePaymentMethods { | ||||
|     pub sdk_eligible_payment_methods: HashSet<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Deserialize, Clone, Default)] | ||||
| pub struct DefaultExchangeRates { | ||||
|     pub base_currency: String, | ||||
|  | ||||
| @ -441,6 +441,8 @@ pub async fn add_payment_method_data( | ||||
|                             status: Some(enums::PaymentMethodStatus::Active), | ||||
|                             locker_id: Some(locker_id), | ||||
|                             payment_method: req.payment_method, | ||||
|                             payment_method_issuer: req.payment_method_issuer, | ||||
|                             payment_method_type: req.payment_method_type, | ||||
|                         }; | ||||
|  | ||||
|                         db.update_payment_method( | ||||
| @ -555,7 +557,7 @@ pub async fn add_payment_method( | ||||
|     match duplication_check { | ||||
|         Some(duplication_check) => match duplication_check { | ||||
|             payment_methods::DataDuplicationCheck::Duplicated => { | ||||
|                 get_or_insert_payment_method( | ||||
|                 let existing_pm = get_or_insert_payment_method( | ||||
|                     db, | ||||
|                     req.clone(), | ||||
|                     &mut resp, | ||||
| @ -564,6 +566,8 @@ pub async fn add_payment_method( | ||||
|                     key_store, | ||||
|                 ) | ||||
|                 .await?; | ||||
|  | ||||
|                 resp.client_secret = existing_pm.client_secret; | ||||
|             } | ||||
|             payment_methods::DataDuplicationCheck::MetaDataChanged => { | ||||
|                 if let Some(card) = req.card.clone() { | ||||
| @ -577,6 +581,8 @@ pub async fn add_payment_method( | ||||
|                     ) | ||||
|                     .await?; | ||||
|  | ||||
|                     let client_secret = existing_pm.client_secret.clone(); | ||||
|  | ||||
|                     delete_card_from_locker( | ||||
|                         &state, | ||||
|                         &customer_id, | ||||
| @ -653,6 +659,8 @@ pub async fn add_payment_method( | ||||
|                     .await | ||||
|                     .change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                     .attach_printable("Failed to add payment method in db")?; | ||||
|  | ||||
|                     resp.client_secret = client_secret; | ||||
|                 } | ||||
|             } | ||||
|         }, | ||||
| @ -667,7 +675,7 @@ pub async fn add_payment_method( | ||||
|                 None | ||||
|             }; | ||||
|             resp.payment_method_id = generate_id(consts::ID_LENGTH, "pm"); | ||||
|             insert_payment_method( | ||||
|             let pm = insert_payment_method( | ||||
|                 db, | ||||
|                 &resp, | ||||
|                 req, | ||||
| @ -682,6 +690,8 @@ pub async fn add_payment_method( | ||||
|                 merchant_account.storage_scheme, | ||||
|             ) | ||||
|             .await?; | ||||
|  | ||||
|             resp.client_secret = pm.client_secret; | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -744,6 +754,18 @@ pub async fn update_customer_payment_method( | ||||
|             .await | ||||
|             .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; | ||||
|  | ||||
|         if pm.status == enums::PaymentMethodStatus::AwaitingData { | ||||
|             return Err(report!(errors::ApiErrorResponse::NotSupported { | ||||
|                 message: "Payment method is awaiting data so it cannot be updated".into() | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         if pm.payment_method_data.is_none() { | ||||
|             return Err(report!(errors::ApiErrorResponse::GenericNotFoundError { | ||||
|                 message: "payment_method_data not found".to_string() | ||||
|             })); | ||||
|         } | ||||
|  | ||||
|         // Fetch the existing payment method data from db | ||||
|         let existing_card_data = decrypt::<serde_json::Value, masking::WithType>( | ||||
|             pm.payment_method_data.clone(), | ||||
| @ -812,7 +834,7 @@ pub async fn update_customer_payment_method( | ||||
|                 wallet: req.wallet, | ||||
|                 metadata: req.metadata, | ||||
|                 customer_id: Some(pm.customer_id.clone()), | ||||
|                 client_secret: None, | ||||
|                 client_secret: pm.client_secret.clone(), | ||||
|                 payment_method_data: None, | ||||
|                 card_network: req | ||||
|                     .card_network | ||||
| @ -901,7 +923,7 @@ pub async fn update_customer_payment_method( | ||||
|                 installment_payment_enabled: false, | ||||
|                 payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), | ||||
|                 last_used_at: Some(common_utils::date_time::now()), | ||||
|                 client_secret: None, | ||||
|                 client_secret: pm.client_secret.clone(), | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @ -1691,12 +1713,21 @@ pub async fn list_payment_methods( | ||||
|     let db = &*state.store; | ||||
|     let pm_config_mapping = &state.conf.pm_filters; | ||||
|  | ||||
|     let payment_intent = helpers::verify_payment_intent_time_and_client_secret( | ||||
|         db, | ||||
|         &merchant_account, | ||||
|         req.client_secret.clone(), | ||||
|     ) | ||||
|     .await?; | ||||
|     let payment_intent = if let Some(cs) = &req.client_secret { | ||||
|         if cs.starts_with("pm_") { | ||||
|             validate_payment_method_and_client_secret(cs, db, &merchant_account).await?; | ||||
|             None | ||||
|         } else { | ||||
|             helpers::verify_payment_intent_time_and_client_secret( | ||||
|                 db, | ||||
|                 &merchant_account, | ||||
|                 req.client_secret.clone(), | ||||
|             ) | ||||
|             .await? | ||||
|         } | ||||
|     } else { | ||||
|         None | ||||
|     }; | ||||
|  | ||||
|     let shipping_address = payment_intent | ||||
|         .as_ref() | ||||
| @ -1839,6 +1870,7 @@ pub async fn list_payment_methods( | ||||
|             pm_config_mapping, | ||||
|             &state.conf.mandates.supported_payment_methods, | ||||
|             &state.conf.mandates.update_mandate_supported, | ||||
|             &state.conf.saved_payment_methods, | ||||
|         ) | ||||
|         .await?; | ||||
|     } | ||||
| @ -2535,6 +2567,34 @@ pub async fn list_payment_methods( | ||||
|     )) | ||||
| } | ||||
|  | ||||
| async fn validate_payment_method_and_client_secret( | ||||
|     cs: &String, | ||||
|     db: &dyn db::StorageInterface, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
| ) -> Result<(), error_stack::Report<errors::ApiErrorResponse>> { | ||||
|     let pm_vec = cs.split("_secret").collect::<Vec<&str>>(); | ||||
|     let pm_id = pm_vec | ||||
|         .first() | ||||
|         .ok_or(errors::ApiErrorResponse::MissingRequiredField { | ||||
|             field_name: "client_secret", | ||||
|         })?; | ||||
|  | ||||
|     let payment_method = db | ||||
|         .find_payment_method(pm_id, merchant_account.storage_scheme) | ||||
|         .await | ||||
|         .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) | ||||
|         .attach_printable("Unable to find payment method")?; | ||||
|  | ||||
|     let client_secret_expired = | ||||
|         authenticate_pm_client_secret_and_check_expiry(cs, &payment_method)?; | ||||
|     if client_secret_expired { | ||||
|         return Err::<(), error_stack::Report<errors::ApiErrorResponse>>( | ||||
|             (errors::ApiErrorResponse::ClientSecretExpired).into(), | ||||
|         ); | ||||
|     } | ||||
|     Ok(()) | ||||
| } | ||||
|  | ||||
| pub async fn call_surcharge_decision_management( | ||||
|     state: routes::AppState, | ||||
|     merchant_account: &domain::MerchantAccount, | ||||
| @ -2644,6 +2704,7 @@ pub async fn filter_payment_methods( | ||||
|     config: &settings::ConnectorFilters, | ||||
|     supported_payment_methods_for_mandate: &settings::SupportedPaymentMethodsForMandate, | ||||
|     supported_payment_methods_for_update_mandate: &settings::SupportedPaymentMethodsForMandate, | ||||
|     saved_payment_methods: &settings::EligiblePaymentMethods, | ||||
| ) -> errors::CustomResult<(), errors::ApiErrorResponse> { | ||||
|     for payment_method in payment_methods.into_iter() { | ||||
|         let parse_result = serde_json::from_value::<PaymentMethodsEnabled>(payment_method); | ||||
| @ -2761,6 +2822,20 @@ pub async fn filter_payment_methods( | ||||
|                         }) | ||||
|                         .unwrap_or(true); | ||||
|  | ||||
|                     let filter9 = req | ||||
|                         .client_secret | ||||
|                         .as_ref() | ||||
|                         .map(|cs| { | ||||
|                             if cs.starts_with("pm_") { | ||||
|                                 saved_payment_methods | ||||
|                                     .sdk_eligible_payment_methods | ||||
|                                     .contains(payment_method.to_string().as_str()) | ||||
|                             } else { | ||||
|                                 true | ||||
|                             } | ||||
|                         }) | ||||
|                         .unwrap_or(true); | ||||
|  | ||||
|                     let connector = connector.clone(); | ||||
|  | ||||
|                     let response_pm_type = ResponsePaymentMethodIntermediate::new( | ||||
| @ -2777,6 +2852,7 @@ pub async fn filter_payment_methods( | ||||
|                         && filter6 | ||||
|                         && filter7 | ||||
|                         && filter8 | ||||
|                         && filter9 | ||||
|                     { | ||||
|                         resp.push(response_pm_type); | ||||
|                     } | ||||
|  | ||||
| @ -374,7 +374,7 @@ pub fn mk_add_card_response_hs( | ||||
|         installment_payment_enabled: false, // #[#256] | ||||
|         payment_experience: Some(vec![api_models::enums::PaymentExperience::RedirectToUrl]), | ||||
|         last_used_at: Some(common_utils::date_time::now()), // [#256] | ||||
|         client_secret: None, | ||||
|         client_secret: req.client_secret, | ||||
|     } | ||||
| } | ||||
|  | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Sarthak Soni
					Sarthak Soni