mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	refactor(mandates): add validations for recurring mandates using payment_method_id (#4263)
This commit is contained in:
		| @ -282,6 +282,28 @@ fn saved_in_locker_default() -> bool { | |||||||
|     true |     true | ||||||
| } | } | ||||||
|  |  | ||||||
|  | impl From<CardDetailFromLocker> for payments::AdditionalCardInfo { | ||||||
|  |     fn from(item: CardDetailFromLocker) -> Self { | ||||||
|  |         Self { | ||||||
|  |             card_issuer: item.card_issuer, | ||||||
|  |             card_network: item.card_network, | ||||||
|  |             card_type: item.card_type, | ||||||
|  |             card_issuing_country: item.issuer_country, | ||||||
|  |             bank_code: None, | ||||||
|  |             last4: item.last4_digits, | ||||||
|  |             card_isin: item.card_isin, | ||||||
|  |             card_extended_bin: item | ||||||
|  |                 .card_number | ||||||
|  |                 .map(|card_number| card_number.get_card_extended_bin()), | ||||||
|  |             card_exp_month: item.expiry_month, | ||||||
|  |             card_exp_year: item.expiry_year, | ||||||
|  |             card_holder_name: item.card_holder_name, | ||||||
|  |             payment_checks: None, | ||||||
|  |             authentication_data: None, | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| impl From<CardDetailsPaymentMethod> for CardDetailFromLocker { | impl From<CardDetailsPaymentMethod> for CardDetailFromLocker { | ||||||
|     fn from(item: CardDetailsPaymentMethod) -> Self { |     fn from(item: CardDetailsPaymentMethod) -> Self { | ||||||
|         Self { |         Self { | ||||||
|  | |||||||
| @ -469,6 +469,18 @@ pub async fn get_token_pm_type_mandate_details( | |||||||
|                                 errors::ApiErrorResponse::PaymentMethodNotFound, |                                 errors::ApiErrorResponse::PaymentMethodNotFound, | ||||||
|                             )?; |                             )?; | ||||||
|  |  | ||||||
|  |                         let customer_id = request | ||||||
|  |                             .customer_id | ||||||
|  |                             .clone() | ||||||
|  |                             .get_required_value("customer_id")?; | ||||||
|  |  | ||||||
|  |                         verify_mandate_details_for_recurring_payments( | ||||||
|  |                             &payment_method_info.merchant_id, | ||||||
|  |                             &merchant_account.merchant_id, | ||||||
|  |                             &payment_method_info.customer_id, | ||||||
|  |                             &customer_id, | ||||||
|  |                         )?; | ||||||
|  |  | ||||||
|                         ( |                         ( | ||||||
|                             None, |                             None, | ||||||
|                             Some(payment_method_info.payment_method), |                             Some(payment_method_info.payment_method), | ||||||
| @ -869,6 +881,7 @@ pub fn validate_mandate( | |||||||
| pub fn validate_recurring_details_and_token( | pub fn validate_recurring_details_and_token( | ||||||
|     recurring_details: &Option<RecurringDetails>, |     recurring_details: &Option<RecurringDetails>, | ||||||
|     payment_token: &Option<String>, |     payment_token: &Option<String>, | ||||||
|  |     mandate_id: &Option<String>, | ||||||
| ) -> CustomResult<(), errors::ApiErrorResponse> { | ) -> CustomResult<(), errors::ApiErrorResponse> { | ||||||
|     utils::when( |     utils::when( | ||||||
|         recurring_details.is_some() && payment_token.is_some(), |         recurring_details.is_some() && payment_token.is_some(), | ||||||
| @ -880,6 +893,12 @@ pub fn validate_recurring_details_and_token( | |||||||
|         }, |         }, | ||||||
|     )?; |     )?; | ||||||
|  |  | ||||||
|  |     utils::when(recurring_details.is_some() && mandate_id.is_some(), || { | ||||||
|  |         Err(report!(errors::ApiErrorResponse::PreconditionFailed { | ||||||
|  |             message: "Expected one out of recurring_details and mandate_id but got both".into() | ||||||
|  |         })) | ||||||
|  |     })?; | ||||||
|  |  | ||||||
|     Ok(()) |     Ok(()) | ||||||
| } | } | ||||||
|  |  | ||||||
| @ -1080,6 +1099,24 @@ pub fn verify_mandate_details( | |||||||
|     ) |     ) | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub fn verify_mandate_details_for_recurring_payments( | ||||||
|  |     mandate_merchant_id: &str, | ||||||
|  |     merchant_id: &str, | ||||||
|  |     mandate_customer_id: &str, | ||||||
|  |     customer_id: &str, | ||||||
|  | ) -> RouterResult<()> { | ||||||
|  |     if mandate_merchant_id != merchant_id { | ||||||
|  |         Err(report!(errors::ApiErrorResponse::MandateNotFound))? | ||||||
|  |     } | ||||||
|  |     if mandate_customer_id != customer_id { | ||||||
|  |         Err(report!(errors::ApiErrorResponse::PreconditionFailed { | ||||||
|  |             message: "customer_id must match mandate customer_id".into() | ||||||
|  |         }))? | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     Ok(()) | ||||||
|  | } | ||||||
|  |  | ||||||
| #[instrument(skip_all)] | #[instrument(skip_all)] | ||||||
| pub fn payment_attempt_status_fsm( | pub fn payment_attempt_status_fsm( | ||||||
|     payment_method_data: &Option<api::payments::PaymentMethodDataRequest>, |     payment_method_data: &Option<api::payments::PaymentMethodDataRequest>, | ||||||
|  | |||||||
| @ -461,6 +461,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen | |||||||
|         helpers::validate_recurring_details_and_token( |         helpers::validate_recurring_details_and_token( | ||||||
|             &request.recurring_details, |             &request.recurring_details, | ||||||
|             &request.payment_token, |             &request.payment_token, | ||||||
|  |             &request.mandate_id, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         Ok(( |         Ok(( | ||||||
|  | |||||||
| @ -1205,6 +1205,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen | |||||||
|         helpers::validate_recurring_details_and_token( |         helpers::validate_recurring_details_and_token( | ||||||
|             &request.recurring_details, |             &request.recurring_details, | ||||||
|             &request.payment_token, |             &request.payment_token, | ||||||
|  |             &request.mandate_id, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         let payment_id = request |         let payment_id = request | ||||||
|  | |||||||
| @ -1,15 +1,17 @@ | |||||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||||
|  |  | ||||||
| use api_models::{enums::FrmSuggestion, mandates::RecurringDetails}; | use api_models::{ | ||||||
|  |     enums::FrmSuggestion, mandates::RecurringDetails, payment_methods::PaymentMethodsData, | ||||||
|  | }; | ||||||
| use async_trait::async_trait; | use async_trait::async_trait; | ||||||
| use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; | use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; | ||||||
| use data_models::{ | use data_models::{ | ||||||
|     mandates::{MandateData, MandateDetails}, |     mandates::{MandateData, MandateDetails}, | ||||||
|     payments::payment_attempt::PaymentAttempt, |     payments::payment_attempt::PaymentAttempt, | ||||||
| }; | }; | ||||||
| use diesel_models::ephemeral_key; | use diesel_models::{ephemeral_key, PaymentMethod}; | ||||||
| use error_stack::{self, ResultExt}; | use error_stack::{self, ResultExt}; | ||||||
| use masking::PeekInterface; | use masking::{ExposeInterface, PeekInterface}; | ||||||
| use router_derive::PaymentOperation; | use router_derive::PaymentOperation; | ||||||
| use router_env::{instrument, tracing}; | use router_env::{instrument, tracing}; | ||||||
| use time::PrimitiveDateTime; | use time::PrimitiveDateTime; | ||||||
| @ -259,6 +261,8 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> | |||||||
|             payment_method_billing_address |             payment_method_billing_address | ||||||
|                 .as_ref() |                 .as_ref() | ||||||
|                 .map(|address| address.address_id.clone()), |                 .map(|address| address.address_id.clone()), | ||||||
|  |             &payment_method_info, | ||||||
|  |             merchant_key_store, | ||||||
|         ) |         ) | ||||||
|         .await?; |         .await?; | ||||||
|  |  | ||||||
| @ -691,6 +695,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen | |||||||
|         helpers::validate_recurring_details_and_token( |         helpers::validate_recurring_details_and_token( | ||||||
|             &request.recurring_details, |             &request.recurring_details, | ||||||
|             &request.payment_token, |             &request.payment_token, | ||||||
|  |             &request.mandate_id, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         if request.confirm.unwrap_or(false) { |         if request.confirm.unwrap_or(false) { | ||||||
| @ -744,6 +749,8 @@ impl PaymentCreate { | |||||||
|         browser_info: Option<serde_json::Value>, |         browser_info: Option<serde_json::Value>, | ||||||
|         state: &AppState, |         state: &AppState, | ||||||
|         payment_method_billing_address_id: Option<String>, |         payment_method_billing_address_id: Option<String>, | ||||||
|  |         payment_method_info: &Option<PaymentMethod>, | ||||||
|  |         key_store: &domain::MerchantKeyStore, | ||||||
|     ) -> RouterResult<( |     ) -> RouterResult<( | ||||||
|         storage::PaymentAttemptNew, |         storage::PaymentAttemptNew, | ||||||
|         Option<api_models::payments::AdditionalPaymentData>, |         Option<api_models::payments::AdditionalPaymentData>, | ||||||
| @ -753,7 +760,7 @@ impl PaymentCreate { | |||||||
|             helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); |             helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); | ||||||
|         let (amount, currency) = (money.0, Some(money.1)); |         let (amount, currency) = (money.0, Some(money.1)); | ||||||
|  |  | ||||||
|         let additional_pm_data = request |         let mut additional_pm_data = request | ||||||
|             .payment_method_data |             .payment_method_data | ||||||
|             .as_ref() |             .as_ref() | ||||||
|             .async_map(|payment_method_data| async { |             .async_map(|payment_method_data| async { | ||||||
| @ -764,6 +771,35 @@ impl PaymentCreate { | |||||||
|                 .await |                 .await | ||||||
|             }) |             }) | ||||||
|             .await; |             .await; | ||||||
|  |  | ||||||
|  |         if additional_pm_data.is_none() { | ||||||
|  |             // If recurring payment is made using payment_method_id, then fetch payment_method_data from retrieved payment_method object | ||||||
|  |             additional_pm_data = payment_method_info | ||||||
|  |                 .as_ref() | ||||||
|  |                 .async_map(|pm_info| async { | ||||||
|  |                     domain::types::decrypt::<serde_json::Value, masking::WithType>( | ||||||
|  |                         pm_info.payment_method_data.clone(), | ||||||
|  |                         key_store.key.get_inner().peek(), | ||||||
|  |                     ) | ||||||
|  |                     .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, | ||||||
|  |                     }) | ||||||
|  |                 }) | ||||||
|  |                 .await | ||||||
|  |                 .flatten() | ||||||
|  |                 .map(|card| { | ||||||
|  |                     api_models::payments::AdditionalPaymentData::Card(Box::new(card.into())) | ||||||
|  |                 }) | ||||||
|  |         }; | ||||||
|  |  | ||||||
|         let additional_pm_data_value = additional_pm_data |         let additional_pm_data_value = additional_pm_data | ||||||
|             .as_ref() |             .as_ref() | ||||||
|             .map(Encode::encode_to_value) |             .map(Encode::encode_to_value) | ||||||
| @ -842,7 +878,9 @@ impl PaymentCreate { | |||||||
|                 connector: None, |                 connector: None, | ||||||
|                 error_message: None, |                 error_message: None, | ||||||
|                 offer_amount: None, |                 offer_amount: None, | ||||||
|                 payment_method_id: None, |                 payment_method_id: payment_method_info | ||||||
|  |                     .as_ref() | ||||||
|  |                     .map(|pm_info| pm_info.payment_method_id.clone()), | ||||||
|                 cancellation_reason: None, |                 cancellation_reason: None, | ||||||
|                 error_code: None, |                 error_code: None, | ||||||
|                 connector_metadata: None, |                 connector_metadata: None, | ||||||
|  | |||||||
| @ -754,6 +754,7 @@ impl<F: Send + Clone, Ctx: PaymentMethodRetrieve> ValidateRequest<F, api::Paymen | |||||||
|         helpers::validate_recurring_details_and_token( |         helpers::validate_recurring_details_and_token( | ||||||
|             &request.recurring_details, |             &request.recurring_details, | ||||||
|             &request.payment_token, |             &request.payment_token, | ||||||
|  |             &request.mandate_id, | ||||||
|         )?; |         )?; | ||||||
|  |  | ||||||
|         Ok(( |         Ok(( | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	 Chethan Rao
					Chethan Rao