diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 591932c5de..cfb45ef045 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -294,6 +294,7 @@ pub struct PaymentMethodRecordUpdateResponse { pub status: common_enums::PaymentMethodStatus, pub network_transaction_id: Option, pub connector_mandate_details: Option, + pub updated_payment_method_data: Option, } #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] @@ -2689,7 +2690,9 @@ pub struct UpdatePaymentMethodRecord { pub network_transaction_id: Option, pub line_number: Option, pub payment_instrument_id: Option>, - pub merchant_connector_id: Option, + pub merchant_connector_ids: Option, + pub card_expiry_month: Option>, + pub card_expiry_year: Option>, } #[derive(Debug, serde::Serialize)] @@ -2701,6 +2704,7 @@ pub struct PaymentMethodUpdateResponse { pub update_status: UpdateStatus, #[serde(skip_serializing_if = "Option::is_none")] pub update_error: Option, + pub updated_payment_method_data: Option, pub line_number: Option, } @@ -2841,6 +2845,7 @@ impl From for PaymentMethodUpdateResponse { status: Some(res.status), network_transaction_id: res.network_transaction_id, connector_mandate_details: res.connector_mandate_details, + updated_payment_method_data: res.updated_payment_method_data, update_status: UpdateStatus::Success, update_error: None, line_number: record.line_number, @@ -2850,6 +2855,7 @@ impl From for PaymentMethodUpdateResponse { status: record.status, network_transaction_id: record.network_transaction_id, connector_mandate_details: None, + updated_payment_method_data: None, update_status: UpdateStatus::Failed, update_error: Some(e), line_number: record.line_number, diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index 747c6fdbd3..edadeea799 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -251,10 +251,11 @@ pub enum PaymentMethodUpdate { connector_mandate_details: Option, network_transaction_id: Option>, }, - ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate { + PaymentMethodBatchUpdate { connector_mandate_details: Option, network_transaction_id: Option, status: Option, + payment_method_data: Option, }, } @@ -687,13 +688,13 @@ impl From for PaymentMethodUpdateInternal { network_token_payment_method_data: None, scheme: None, }, - PaymentMethodUpdate::ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate { + PaymentMethodUpdate::PaymentMethodBatchUpdate { connector_mandate_details, network_transaction_id, status, + payment_method_data, } => Self { metadata: None, - payment_method_data: None, last_used_at: None, status, locker_id: None, @@ -709,6 +710,7 @@ impl From for PaymentMethodUpdateInternal { network_token_locker_id: None, network_token_payment_method_data: None, scheme: None, + payment_method_data, }, } } diff --git a/crates/payment_methods/src/core/migration.rs b/crates/payment_methods/src/core/migration.rs index ea9b6db103..c077ddb2cf 100644 --- a/crates/payment_methods/src/core/migration.rs +++ b/crates/payment_methods/src/core/migration.rs @@ -77,10 +77,10 @@ pub struct PaymentMethodsMigrateForm { pub merchant_connector_ids: Option>, } -struct MerchantConnectorValidator; +pub struct MerchantConnectorValidator; impl MerchantConnectorValidator { - fn parse_comma_separated_ids( + pub fn parse_comma_separated_ids( ids_string: &str, ) -> Result, errors::ApiErrorResponse> { diff --git a/crates/router/src/core/payment_methods/migration.rs b/crates/router/src/core/payment_methods/migration.rs index d802e432ec..26a2d1737c 100644 --- a/crates/router/src/core/payment_methods/migration.rs +++ b/crates/router/src/core/payment_methods/migration.rs @@ -7,12 +7,15 @@ use hyperswitch_domain_models::{ api::ApplicationResponse, errors::api_error_response as errors, merchant_context, payment_methods::PaymentMethodUpdate, }; -use masking::PeekInterface; +use masking::{ExposeInterface, PeekInterface}; +use payment_methods::core::migration::MerchantConnectorValidator; use rdkafka::message::ToBytes; use router_env::logger; -use crate::{core::errors::StorageErrorExt, routes::SessionState}; - +use crate::{ + core::{errors::StorageErrorExt, payment_methods::cards::create_encrypted_data}, + routes::SessionState, +}; type PmMigrationResult = CustomResult, errors::ApiErrorResponse>; #[cfg(feature = "v1")] @@ -62,6 +65,8 @@ pub async fn update_payment_method_record( let payment_method_id = req.payment_method_id.clone(); let network_transaction_id = req.network_transaction_id.clone(); let status = req.status; + let key_manager_state = state.into(); + let mut updated_card_expiry = false; let payment_method = db .find_payment_method( @@ -79,94 +84,143 @@ pub async fn update_payment_method_record( }.into()); } - // Process mandate details when both payment_instrument_id and merchant_connector_id are present - let pm_update = match (&req.payment_instrument_id, &req.merchant_connector_id) { - (Some(payment_instrument_id), Some(merchant_connector_id)) => { + let updated_payment_method_data = match payment_method.payment_method_data.as_ref() { + Some(data) => { + match serde_json::from_value::( + data.clone().into_inner().expose(), + ) { + Ok(pm_api::PaymentMethodsData::Card(mut card_data)) => { + if let Some(new_month) = &req.card_expiry_month { + card_data.expiry_month = Some(new_month.clone()); + updated_card_expiry = true; + } + + if let Some(new_year) = &req.card_expiry_year { + card_data.expiry_year = Some(new_year.clone()); + updated_card_expiry = true; + } + + if updated_card_expiry { + Some( + create_encrypted_data( + &key_manager_state, + merchant_context.get_merchant_key_store(), + pm_api::PaymentMethodsData::Card(card_data), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt payment method data") + .map(Into::into), + ) + } else { + None + } + } + _ => None, + } + } + None => None, + } + .transpose()?; + + // Process mandate details when both payment_instrument_id and merchant_connector_ids are present + let pm_update = match ( + &req.payment_instrument_id, + &req.merchant_connector_ids.clone(), + ) { + (Some(payment_instrument_id), Some(merchant_connector_ids)) => { + let parsed_mca_ids = + MerchantConnectorValidator::parse_comma_separated_ids(merchant_connector_ids)?; let mandate_details = payment_method .get_common_mandate_reference() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; - let mca = db - .find_by_merchant_connector_account_merchant_id_merchant_connector_id( - &state.into(), - merchant_context.get_merchant_account().get_id(), - merchant_connector_id, - merchant_context.get_merchant_key_store(), - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::MerchantConnectorAccountNotFound { - id: merchant_connector_id.get_string_repr().to_string(), - }, - )?; + let mut existing_payments_mandate = mandate_details + .payments + .clone() + .unwrap_or(PaymentsMandateReference(HashMap::new())); + let mut existing_payouts_mandate = mandate_details + .payouts + .clone() + .unwrap_or(PayoutsMandateReference(HashMap::new())); - let updated_connector_mandate_details = match mca.connector_type { - enums::ConnectorType::PayoutProcessor => { - // Handle PayoutsMandateReference - let mut existing_payouts_mandate = mandate_details - .payouts - .unwrap_or_else(|| PayoutsMandateReference(HashMap::new())); + for merchant_connector_id in parsed_mca_ids { + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + &state.into(), + merchant_context.get_merchant_account().get_id(), + &merchant_connector_id, + merchant_context.get_merchant_key_store(), + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: merchant_connector_id.get_string_repr().to_string(), + }, + )?; - // Create new payout mandate record - let new_payout_record = PayoutsMandateReferenceRecord { - transfer_method_id: Some(payment_instrument_id.peek().to_string()), - }; + match mca.connector_type { + enums::ConnectorType::PayoutProcessor => { + // Handle PayoutsMandateReference + let new_payout_record = PayoutsMandateReferenceRecord { + transfer_method_id: Some(payment_instrument_id.peek().to_string()), + }; - // Check if record exists for this merchant_connector_id - if let Some(existing_record) = - existing_payouts_mandate.0.get_mut(merchant_connector_id) - { - if let Some(transfer_method_id) = &new_payout_record.transfer_method_id { - existing_record.transfer_method_id = Some(transfer_method_id.clone()); + // Check if record exists for this merchant_connector_id + if let Some(existing_record) = + existing_payouts_mandate.0.get_mut(&merchant_connector_id) + { + if let Some(transfer_method_id) = &new_payout_record.transfer_method_id + { + existing_record.transfer_method_id = + Some(transfer_method_id.clone()); + } + } else { + // Insert new record in connector_mandate_details + existing_payouts_mandate + .0 + .insert(merchant_connector_id.clone(), new_payout_record); } - } else { - // Insert new record in connector_mandate_details - existing_payouts_mandate - .0 - .insert(merchant_connector_id.clone(), new_payout_record); } - - // Create updated CommonMandateReference preserving payments section - CommonMandateReference { - payments: mandate_details.payments, - payouts: Some(existing_payouts_mandate), + _ => { + // Handle PaymentsMandateReference + // Check if record exists for this merchant_connector_id + if let Some(existing_record) = + existing_payments_mandate.0.get_mut(&merchant_connector_id) + { + existing_record.connector_mandate_id = + payment_instrument_id.peek().to_string(); + } else { + // Insert new record in connector_mandate_details + existing_payments_mandate.0.insert( + merchant_connector_id.clone(), + PaymentsMandateReferenceRecord { + connector_mandate_id: payment_instrument_id.peek().to_string(), + payment_method_type: None, + original_payment_authorized_amount: None, + original_payment_authorized_currency: None, + mandate_metadata: None, + connector_mandate_status: None, + connector_mandate_request_reference_id: None, + }, + ); + } } } - _ => { - // Handle PaymentsMandateReference - let mut existing_payments_mandate = mandate_details - .payments - .unwrap_or_else(|| PaymentsMandateReference(HashMap::new())); + } - // Check if record exists for this merchant_connector_id - if let Some(existing_record) = - existing_payments_mandate.0.get_mut(merchant_connector_id) - { - existing_record.connector_mandate_id = - payment_instrument_id.peek().to_string(); - } else { - // Insert new record in connector_mandate_details - existing_payments_mandate.0.insert( - merchant_connector_id.clone(), - PaymentsMandateReferenceRecord { - connector_mandate_id: payment_instrument_id.peek().to_string(), - payment_method_type: None, - original_payment_authorized_amount: None, - original_payment_authorized_currency: None, - mandate_metadata: None, - connector_mandate_status: None, - connector_mandate_request_reference_id: None, - }, - ); - } - - // Create updated CommonMandateReference preserving payouts section - CommonMandateReference { - payments: Some(existing_payments_mandate), - payouts: mandate_details.payouts, - } - } + let updated_connector_mandate_details = CommonMandateReference { + payments: if !existing_payments_mandate.0.is_empty() { + Some(existing_payments_mandate) + } else { + mandate_details.payments + }, + payouts: if !existing_payouts_mandate.0.is_empty() { + Some(existing_payouts_mandate) + } else { + mandate_details.payouts + }, }; let connector_mandate_details_value = updated_connector_mandate_details @@ -176,19 +230,25 @@ pub async fn update_payment_method_record( errors::ApiErrorResponse::MandateUpdateFailed })?; - PaymentMethodUpdate::ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate { + PaymentMethodUpdate::PaymentMethodBatchUpdate { connector_mandate_details: Some(pii::SecretSerdeValue::new( connector_mandate_details_value, )), network_transaction_id, status, + payment_method_data: updated_payment_method_data.clone(), } } _ => { - // Update only network_transaction_id and status - PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate { - network_transaction_id, - status, + if updated_payment_method_data.is_some() { + PaymentMethodUpdate::PaymentMethodDataUpdate { + payment_method_data: updated_payment_method_data, + } + } else { + PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate { + network_transaction_id, + status, + } } } }; @@ -217,6 +277,7 @@ pub async fn update_payment_method_record( connector_mandate_details: response .connector_mandate_details .map(pii::SecretSerdeValue::new), + updated_payment_method_data: Some(updated_card_expiry), }, )) }