mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-28 04:04:55 +08:00
feat(core): Add support to update card exp in update payment methods api (#9688)
This commit is contained in:
@ -294,6 +294,7 @@ pub struct PaymentMethodRecordUpdateResponse {
|
|||||||
pub status: common_enums::PaymentMethodStatus,
|
pub status: common_enums::PaymentMethodStatus,
|
||||||
pub network_transaction_id: Option<String>,
|
pub network_transaction_id: Option<String>,
|
||||||
pub connector_mandate_details: Option<pii::SecretSerdeValue>,
|
pub connector_mandate_details: Option<pii::SecretSerdeValue>,
|
||||||
|
pub updated_payment_method_data: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)]
|
||||||
@ -2689,7 +2690,9 @@ pub struct UpdatePaymentMethodRecord {
|
|||||||
pub network_transaction_id: Option<String>,
|
pub network_transaction_id: Option<String>,
|
||||||
pub line_number: Option<i64>,
|
pub line_number: Option<i64>,
|
||||||
pub payment_instrument_id: Option<masking::Secret<String>>,
|
pub payment_instrument_id: Option<masking::Secret<String>>,
|
||||||
pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
|
pub merchant_connector_ids: Option<String>,
|
||||||
|
pub card_expiry_month: Option<masking::Secret<String>>,
|
||||||
|
pub card_expiry_year: Option<masking::Secret<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, serde::Serialize)]
|
#[derive(Debug, serde::Serialize)]
|
||||||
@ -2701,6 +2704,7 @@ pub struct PaymentMethodUpdateResponse {
|
|||||||
pub update_status: UpdateStatus,
|
pub update_status: UpdateStatus,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub update_error: Option<String>,
|
pub update_error: Option<String>,
|
||||||
|
pub updated_payment_method_data: Option<bool>,
|
||||||
pub line_number: Option<i64>,
|
pub line_number: Option<i64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2841,6 +2845,7 @@ impl From<PaymentMethodUpdateResponseType> for PaymentMethodUpdateResponse {
|
|||||||
status: Some(res.status),
|
status: Some(res.status),
|
||||||
network_transaction_id: res.network_transaction_id,
|
network_transaction_id: res.network_transaction_id,
|
||||||
connector_mandate_details: res.connector_mandate_details,
|
connector_mandate_details: res.connector_mandate_details,
|
||||||
|
updated_payment_method_data: res.updated_payment_method_data,
|
||||||
update_status: UpdateStatus::Success,
|
update_status: UpdateStatus::Success,
|
||||||
update_error: None,
|
update_error: None,
|
||||||
line_number: record.line_number,
|
line_number: record.line_number,
|
||||||
@ -2850,6 +2855,7 @@ impl From<PaymentMethodUpdateResponseType> for PaymentMethodUpdateResponse {
|
|||||||
status: record.status,
|
status: record.status,
|
||||||
network_transaction_id: record.network_transaction_id,
|
network_transaction_id: record.network_transaction_id,
|
||||||
connector_mandate_details: None,
|
connector_mandate_details: None,
|
||||||
|
updated_payment_method_data: None,
|
||||||
update_status: UpdateStatus::Failed,
|
update_status: UpdateStatus::Failed,
|
||||||
update_error: Some(e),
|
update_error: Some(e),
|
||||||
line_number: record.line_number,
|
line_number: record.line_number,
|
||||||
|
|||||||
@ -251,10 +251,11 @@ pub enum PaymentMethodUpdate {
|
|||||||
connector_mandate_details: Option<pii::SecretSerdeValue>,
|
connector_mandate_details: Option<pii::SecretSerdeValue>,
|
||||||
network_transaction_id: Option<Secret<String>>,
|
network_transaction_id: Option<Secret<String>>,
|
||||||
},
|
},
|
||||||
ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate {
|
PaymentMethodBatchUpdate {
|
||||||
connector_mandate_details: Option<pii::SecretSerdeValue>,
|
connector_mandate_details: Option<pii::SecretSerdeValue>,
|
||||||
network_transaction_id: Option<String>,
|
network_transaction_id: Option<String>,
|
||||||
status: Option<storage_enums::PaymentMethodStatus>,
|
status: Option<storage_enums::PaymentMethodStatus>,
|
||||||
|
payment_method_data: Option<Encryption>,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -687,13 +688,13 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
|
|||||||
network_token_payment_method_data: None,
|
network_token_payment_method_data: None,
|
||||||
scheme: None,
|
scheme: None,
|
||||||
},
|
},
|
||||||
PaymentMethodUpdate::ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate {
|
PaymentMethodUpdate::PaymentMethodBatchUpdate {
|
||||||
connector_mandate_details,
|
connector_mandate_details,
|
||||||
network_transaction_id,
|
network_transaction_id,
|
||||||
status,
|
status,
|
||||||
|
payment_method_data,
|
||||||
} => Self {
|
} => Self {
|
||||||
metadata: None,
|
metadata: None,
|
||||||
payment_method_data: None,
|
|
||||||
last_used_at: None,
|
last_used_at: None,
|
||||||
status,
|
status,
|
||||||
locker_id: None,
|
locker_id: None,
|
||||||
@ -709,6 +710,7 @@ impl From<PaymentMethodUpdate> for PaymentMethodUpdateInternal {
|
|||||||
network_token_locker_id: None,
|
network_token_locker_id: None,
|
||||||
network_token_payment_method_data: None,
|
network_token_payment_method_data: None,
|
||||||
scheme: None,
|
scheme: None,
|
||||||
|
payment_method_data,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,10 +77,10 @@ pub struct PaymentMethodsMigrateForm {
|
|||||||
pub merchant_connector_ids: Option<text::Text<String>>,
|
pub merchant_connector_ids: Option<text::Text<String>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MerchantConnectorValidator;
|
pub struct MerchantConnectorValidator;
|
||||||
|
|
||||||
impl MerchantConnectorValidator {
|
impl MerchantConnectorValidator {
|
||||||
fn parse_comma_separated_ids(
|
pub fn parse_comma_separated_ids(
|
||||||
ids_string: &str,
|
ids_string: &str,
|
||||||
) -> Result<Vec<common_utils::id_type::MerchantConnectorAccountId>, errors::ApiErrorResponse>
|
) -> Result<Vec<common_utils::id_type::MerchantConnectorAccountId>, errors::ApiErrorResponse>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -7,12 +7,15 @@ use hyperswitch_domain_models::{
|
|||||||
api::ApplicationResponse, errors::api_error_response as errors, merchant_context,
|
api::ApplicationResponse, errors::api_error_response as errors, merchant_context,
|
||||||
payment_methods::PaymentMethodUpdate,
|
payment_methods::PaymentMethodUpdate,
|
||||||
};
|
};
|
||||||
use masking::PeekInterface;
|
use masking::{ExposeInterface, PeekInterface};
|
||||||
|
use payment_methods::core::migration::MerchantConnectorValidator;
|
||||||
use rdkafka::message::ToBytes;
|
use rdkafka::message::ToBytes;
|
||||||
use router_env::logger;
|
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<T> = CustomResult<ApplicationResponse<T>, errors::ApiErrorResponse>;
|
type PmMigrationResult<T> = CustomResult<ApplicationResponse<T>, errors::ApiErrorResponse>;
|
||||||
|
|
||||||
#[cfg(feature = "v1")]
|
#[cfg(feature = "v1")]
|
||||||
@ -62,6 +65,8 @@ pub async fn update_payment_method_record(
|
|||||||
let payment_method_id = req.payment_method_id.clone();
|
let payment_method_id = req.payment_method_id.clone();
|
||||||
let network_transaction_id = req.network_transaction_id.clone();
|
let network_transaction_id = req.network_transaction_id.clone();
|
||||||
let status = req.status;
|
let status = req.status;
|
||||||
|
let key_manager_state = state.into();
|
||||||
|
let mut updated_card_expiry = false;
|
||||||
|
|
||||||
let payment_method = db
|
let payment_method = db
|
||||||
.find_payment_method(
|
.find_payment_method(
|
||||||
@ -79,94 +84,143 @@ pub async fn update_payment_method_record(
|
|||||||
}.into());
|
}.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Process mandate details when both payment_instrument_id and merchant_connector_id are present
|
let updated_payment_method_data = match payment_method.payment_method_data.as_ref() {
|
||||||
let pm_update = match (&req.payment_instrument_id, &req.merchant_connector_id) {
|
Some(data) => {
|
||||||
(Some(payment_instrument_id), Some(merchant_connector_id)) => {
|
match serde_json::from_value::<pm_api::PaymentMethodsData>(
|
||||||
|
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
|
let mandate_details = payment_method
|
||||||
.get_common_mandate_reference()
|
.get_common_mandate_reference()
|
||||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||||
.attach_printable("Failed to deserialize to Payment Mandate Reference ")?;
|
.attach_printable("Failed to deserialize to Payment Mandate Reference ")?;
|
||||||
|
|
||||||
let mca = db
|
let mut existing_payments_mandate = mandate_details
|
||||||
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
.payments
|
||||||
&state.into(),
|
.clone()
|
||||||
merchant_context.get_merchant_account().get_id(),
|
.unwrap_or(PaymentsMandateReference(HashMap::new()));
|
||||||
merchant_connector_id,
|
let mut existing_payouts_mandate = mandate_details
|
||||||
merchant_context.get_merchant_key_store(),
|
.payouts
|
||||||
)
|
.clone()
|
||||||
.await
|
.unwrap_or(PayoutsMandateReference(HashMap::new()));
|
||||||
.to_not_found_response(
|
|
||||||
errors::ApiErrorResponse::MerchantConnectorAccountNotFound {
|
|
||||||
id: merchant_connector_id.get_string_repr().to_string(),
|
|
||||||
},
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let updated_connector_mandate_details = match mca.connector_type {
|
for merchant_connector_id in parsed_mca_ids {
|
||||||
enums::ConnectorType::PayoutProcessor => {
|
let mca = db
|
||||||
// Handle PayoutsMandateReference
|
.find_by_merchant_connector_account_merchant_id_merchant_connector_id(
|
||||||
let mut existing_payouts_mandate = mandate_details
|
&state.into(),
|
||||||
.payouts
|
merchant_context.get_merchant_account().get_id(),
|
||||||
.unwrap_or_else(|| PayoutsMandateReference(HashMap::new()));
|
&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
|
match mca.connector_type {
|
||||||
let new_payout_record = PayoutsMandateReferenceRecord {
|
enums::ConnectorType::PayoutProcessor => {
|
||||||
transfer_method_id: Some(payment_instrument_id.peek().to_string()),
|
// 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
|
// Check if record exists for this merchant_connector_id
|
||||||
if let Some(existing_record) =
|
if let Some(existing_record) =
|
||||||
existing_payouts_mandate.0.get_mut(merchant_connector_id)
|
existing_payouts_mandate.0.get_mut(&merchant_connector_id)
|
||||||
{
|
{
|
||||||
if let Some(transfer_method_id) = &new_payout_record.transfer_method_id {
|
if let Some(transfer_method_id) = &new_payout_record.transfer_method_id
|
||||||
existing_record.transfer_method_id = Some(transfer_method_id.clone());
|
{
|
||||||
|
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
|
// Handle PaymentsMandateReference
|
||||||
CommonMandateReference {
|
// Check if record exists for this merchant_connector_id
|
||||||
payments: mandate_details.payments,
|
if let Some(existing_record) =
|
||||||
payouts: Some(existing_payouts_mandate),
|
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
|
let updated_connector_mandate_details = CommonMandateReference {
|
||||||
if let Some(existing_record) =
|
payments: if !existing_payments_mandate.0.is_empty() {
|
||||||
existing_payments_mandate.0.get_mut(merchant_connector_id)
|
Some(existing_payments_mandate)
|
||||||
{
|
} else {
|
||||||
existing_record.connector_mandate_id =
|
mandate_details.payments
|
||||||
payment_instrument_id.peek().to_string();
|
},
|
||||||
} else {
|
payouts: if !existing_payouts_mandate.0.is_empty() {
|
||||||
// Insert new record in connector_mandate_details
|
Some(existing_payouts_mandate)
|
||||||
existing_payments_mandate.0.insert(
|
} else {
|
||||||
merchant_connector_id.clone(),
|
mandate_details.payouts
|
||||||
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 connector_mandate_details_value = updated_connector_mandate_details
|
let connector_mandate_details_value = updated_connector_mandate_details
|
||||||
@ -176,19 +230,25 @@ pub async fn update_payment_method_record(
|
|||||||
errors::ApiErrorResponse::MandateUpdateFailed
|
errors::ApiErrorResponse::MandateUpdateFailed
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
PaymentMethodUpdate::ConnectorNetworkTransactionIdStatusAndMandateDetailsUpdate {
|
PaymentMethodUpdate::PaymentMethodBatchUpdate {
|
||||||
connector_mandate_details: Some(pii::SecretSerdeValue::new(
|
connector_mandate_details: Some(pii::SecretSerdeValue::new(
|
||||||
connector_mandate_details_value,
|
connector_mandate_details_value,
|
||||||
)),
|
)),
|
||||||
network_transaction_id,
|
network_transaction_id,
|
||||||
status,
|
status,
|
||||||
|
payment_method_data: updated_payment_method_data.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
// Update only network_transaction_id and status
|
if updated_payment_method_data.is_some() {
|
||||||
PaymentMethodUpdate::NetworkTransactionIdAndStatusUpdate {
|
PaymentMethodUpdate::PaymentMethodDataUpdate {
|
||||||
network_transaction_id,
|
payment_method_data: updated_payment_method_data,
|
||||||
status,
|
}
|
||||||
|
} 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: response
|
||||||
.connector_mandate_details
|
.connector_mandate_details
|
||||||
.map(pii::SecretSerdeValue::new),
|
.map(pii::SecretSerdeValue::new),
|
||||||
|
updated_payment_method_data: Some(updated_card_expiry),
|
||||||
},
|
},
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user