diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index e7a0ddd1ef..dd6495bb97 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -282,6 +282,28 @@ fn saved_in_locker_default() -> bool { true } +impl From 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 for CardDetailFromLocker { fn from(item: CardDetailsPaymentMethod) -> Self { Self { diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4820db9484..51b9504189 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -469,6 +469,18 @@ pub async fn get_token_pm_type_mandate_details( 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, Some(payment_method_info.payment_method), @@ -869,6 +881,7 @@ pub fn validate_mandate( pub fn validate_recurring_details_and_token( recurring_details: &Option, payment_token: &Option, + mandate_id: &Option, ) -> CustomResult<(), errors::ApiErrorResponse> { utils::when( 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(()) } @@ -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)] pub fn payment_attempt_status_fsm( payment_method_data: &Option, diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index 9d0971982a..ccefb3b2b5 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -461,6 +461,7 @@ impl ValidateRequest ValidateRequest payment_method_billing_address .as_ref() .map(|address| address.address_id.clone()), + &payment_method_info, + merchant_key_store, ) .await?; @@ -691,6 +695,7 @@ impl ValidateRequest, state: &AppState, payment_method_billing_address_id: Option, + payment_method_info: &Option, + key_store: &domain::MerchantKeyStore, ) -> RouterResult<( storage::PaymentAttemptNew, Option, @@ -753,7 +760,7 @@ impl PaymentCreate { helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); let (amount, currency) = (money.0, Some(money.1)); - let additional_pm_data = request + let mut additional_pm_data = request .payment_method_data .as_ref() .async_map(|payment_method_data| async { @@ -764,6 +771,35 @@ impl PaymentCreate { .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::( + 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::(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 .as_ref() .map(Encode::encode_to_value) @@ -842,7 +878,9 @@ impl PaymentCreate { connector: None, error_message: 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, error_code: None, connector_metadata: None, diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index a6a41732f1..76c28307db 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -754,6 +754,7 @@ impl ValidateRequest