refactor(router): Make original_payment_authorized_currency and original_payment_authorized_amount mandatory fields for Discover cards and Cybersource connector during payment method migration. (#5370)

This commit is contained in:
Shankar Singh C
2024-07-22 12:38:44 +05:30
committed by GitHub
parent 1c825f465a
commit 06f1406cbc
3 changed files with 176 additions and 145 deletions

View File

@ -1428,12 +1428,8 @@ impl From<PaymentMethodRecord> for PaymentMethodMigrate {
PaymentsMandateReferenceRecord { PaymentsMandateReferenceRecord {
connector_mandate_id: record.payment_instrument_id.peek().to_string(), connector_mandate_id: record.payment_instrument_id.peek().to_string(),
payment_method_type: record.payment_method_type, payment_method_type: record.payment_method_type,
original_payment_authorized_amount: record original_payment_authorized_amount: record.original_transaction_amount,
.original_transaction_amount original_payment_authorized_currency: record.original_transaction_currency,
.or(Some(1000)),
original_payment_authorized_currency: record
.original_transaction_currency
.or(Some(common_enums::Currency::USD)),
}, },
); );
Self { Self {

View File

@ -278,17 +278,30 @@ pub async fn migrate_payment_method(
merchant_account: &domain::MerchantAccount, merchant_account: &domain::MerchantAccount,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
) -> errors::RouterResponse<api::PaymentMethodResponse> { ) -> errors::RouterResponse<api::PaymentMethodResponse> {
let mut req = req;
let card_details = req.card.as_ref().get_required_value("card")?; let card_details = req.card.as_ref().get_required_value("card")?;
let card_number_validation_result = let card_number_validation_result =
cards::CardNumber::from_str(card_details.card_number.peek()); cards::CardNumber::from_str(card_details.card_number.peek());
let card_bin_details =
populate_bin_details_for_masked_card(card_details, &*state.store).await?;
req.card = Some(api_models::payment_methods::MigrateCardDetail {
card_issuing_country: card_bin_details.issuer_country.clone(),
card_network: card_bin_details.card_network.clone(),
card_issuer: card_bin_details.card_issuer.clone(),
card_type: card_bin_details.card_type.clone(),
..card_details.clone()
});
if let Some(connector_mandate_details) = &req.connector_mandate_details { if let Some(connector_mandate_details) = &req.connector_mandate_details {
helpers::validate_merchant_connector_ids_in_connector_mandate_details( helpers::validate_merchant_connector_ids_in_connector_mandate_details(
&state, &state,
key_store, key_store,
connector_mandate_details, connector_mandate_details,
merchant_id, merchant_id,
card_bin_details.card_network.clone(),
) )
.await?; .await?;
}; };
@ -316,18 +329,130 @@ pub async fn migrate_payment_method(
merchant_id.into(), merchant_id.into(),
key_store, key_store,
merchant_account, merchant_account,
card_bin_details.clone(),
) )
.await .await
} }
} }
} }
pub async fn populate_bin_details_for_masked_card(
card_details: &api_models::payment_methods::MigrateCardDetail,
db: &dyn db::StorageInterface,
) -> errors::CustomResult<api::CardDetailFromLocker, errors::ApiErrorResponse> {
helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?;
let card_number = card_details.card_number.clone();
let (card_isin, _last4_digits) = get_card_bin_and_last4_digits_for_masked_card(
card_number.peek(),
)
.change_context(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid card number".to_string(),
})?;
let card_bin_details = if card_details.card_issuer.is_some()
&& card_details.card_network.is_some()
&& card_details.card_type.is_some()
&& card_details.card_issuing_country.is_some()
{
api_models::payment_methods::CardDetailFromLocker::foreign_try_from((card_details, None))?
} else {
let card_info = db
.get_card_info(&card_isin)
.await
.map_err(|error| services::logger::error!(card_info_error=?error))
.ok()
.flatten();
api_models::payment_methods::CardDetailFromLocker::foreign_try_from((
card_details,
card_info,
))?
};
Ok(card_bin_details)
}
impl
ForeignTryFrom<(
&api_models::payment_methods::MigrateCardDetail,
Option<diesel_models::CardInfo>,
)> for api_models::payment_methods::CardDetailFromLocker
{
type Error = error_stack::Report<errors::ApiErrorResponse>;
fn foreign_try_from(
(card_details, card_info): (
&api_models::payment_methods::MigrateCardDetail,
Option<diesel_models::CardInfo>,
),
) -> Result<Self, Self::Error> {
let (card_isin, last4_digits) =
get_card_bin_and_last4_digits_for_masked_card(card_details.card_number.peek())
.change_context(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid card number".to_string(),
})?;
if let Some(card_bin_info) = card_info {
Ok(Self {
scheme: card_details
.card_network
.clone()
.or(card_bin_info.card_network.clone())
.map(|card_network| card_network.to_string()),
last4_digits: Some(last4_digits.clone()),
issuer_country: card_details
.card_issuing_country
.clone()
.or(card_bin_info.card_issuing_country),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin.clone()),
card_issuer: card_details
.card_issuer
.clone()
.or(card_bin_info.card_issuer),
card_network: card_details
.card_network
.clone()
.or(card_bin_info.card_network),
card_type: card_details.card_type.clone().or(card_bin_info.card_type),
saved_to_locker: false,
})
} else {
Ok(Self {
scheme: card_details
.card_network
.clone()
.map(|card_network| card_network.to_string()),
last4_digits: Some(last4_digits.clone()),
issuer_country: card_details.card_issuing_country.clone(),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin.clone()),
card_issuer: card_details.card_issuer.clone(),
card_network: card_details.card_network.clone(),
card_type: card_details.card_type.clone(),
saved_to_locker: false,
})
}
}
}
pub async fn skip_locker_call_and_migrate_payment_method( pub async fn skip_locker_call_and_migrate_payment_method(
state: routes::SessionState, state: routes::SessionState,
req: &api::PaymentMethodMigrate, req: &api::PaymentMethodMigrate,
merchant_id: String, merchant_id: String,
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
merchant_account: &domain::MerchantAccount, merchant_account: &domain::MerchantAccount,
card: api_models::payment_methods::CardDetailFromLocker,
) -> errors::RouterResponse<api::PaymentMethodResponse> { ) -> errors::RouterResponse<api::PaymentMethodResponse> {
let db = &*state.store; let db = &*state.store;
let customer_id = req.customer_id.clone().get_required_value("customer_id")?; let customer_id = req.customer_id.clone().get_required_value("customer_id")?;
@ -362,132 +487,15 @@ pub async fn skip_locker_call_and_migrate_payment_method(
.await .await
.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
let card = if let Some(card_details) = &req.card { let payment_method_card_details =
helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?; PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone()));
let card_number = card_details.card_number.clone();
let (card_isin, last4_digits) = get_card_bin_and_last4_digits_for_masked_card( let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = Some(
card_number.peek(), create_encrypted_data(&state, key_store, payment_method_card_details)
)
.change_context(errors::ApiErrorResponse::InvalidRequestData {
message: "Invalid card number".to_string(),
})?;
if card_details.card_issuer.is_some()
&& card_details.card_network.is_some()
&& card_details.card_type.is_some()
&& card_details.card_issuing_country.is_some()
{
Some(api::CardDetailFromLocker {
scheme: card_details
.card_network
.clone()
.or(card_details.card_network.clone())
.map(|card_network| card_network.to_string()),
last4_digits: Some(last4_digits.clone()),
issuer_country: card_details
.card_issuing_country
.clone()
.or(card_details.card_issuing_country.clone()),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin.clone()),
card_issuer: card_details
.card_issuer
.clone()
.or(card_details.card_issuer.clone()),
card_network: card_details
.card_network
.clone()
.or(card_details.card_network.clone()),
card_type: card_details
.card_type
.clone()
.or(card_details.card_type.clone()),
saved_to_locker: false,
})
} else {
Some(
db.get_card_info(&card_isin)
.await
.map_err(|error| services::logger::error!(card_info_error=?error))
.ok()
.flatten()
.map(|card_info| {
logger::debug!("Fetching bin details");
api::CardDetailFromLocker {
scheme: card_details
.card_network
.clone()
.or(card_info.card_network.clone())
.map(|card_network| card_network.to_string()),
last4_digits: Some(last4_digits.clone()),
issuer_country: card_details
.card_issuing_country
.clone()
.or(card_info.card_issuing_country),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin.clone()),
card_issuer: card_details.card_issuer.clone().or(card_info.card_issuer),
card_network: card_details
.card_network
.clone()
.or(card_info.card_network),
card_type: card_details.card_type.clone().or(card_info.card_type),
saved_to_locker: false,
}
})
.unwrap_or_else(|| {
logger::debug!("Failed to fetch bin details");
api::CardDetailFromLocker {
scheme: card_details
.card_network
.clone()
.map(|card_network| card_network.to_string()),
last4_digits: Some(last4_digits.clone()),
issuer_country: card_details.card_issuing_country.clone(),
card_number: None,
expiry_month: Some(card_details.card_exp_month.clone()),
expiry_year: Some(card_details.card_exp_year.clone()),
card_token: None,
card_fingerprint: None,
card_holder_name: card_details.card_holder_name.clone(),
nick_name: card_details.nick_name.clone(),
card_isin: Some(card_isin.clone()),
card_issuer: card_details.card_issuer.clone(),
card_network: card_details.card_network.clone(),
card_type: card_details.card_type.clone(),
saved_to_locker: false,
}
}),
)
}
} else {
None
};
let payment_method_card_details = card
.as_ref()
.map(|card| PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.clone())));
let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> =
payment_method_card_details
.async_map(|card_details| create_encrypted_data(&state, key_store, card_details))
.await .await
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError) .change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt Payment method card details")?; .attach_printable("Unable to encrypt Payment method card details")?,
);
let payment_method_metadata: Option<serde_json::Value> = let payment_method_metadata: Option<serde_json::Value> =
req.metadata.as_ref().map(|data| data.peek()).cloned(); req.metadata.as_ref().map(|data| data.peek()).cloned();
@ -506,10 +514,7 @@ pub async fn skip_locker_call_and_migrate_payment_method(
payment_method: req.payment_method, payment_method: req.payment_method,
payment_method_type: req.payment_method_type, payment_method_type: req.payment_method_type,
payment_method_issuer: req.payment_method_issuer.clone(), payment_method_issuer: req.payment_method_issuer.clone(),
scheme: req scheme: req.card_network.clone().or(card.scheme.clone()),
.card_network
.clone()
.or(card.clone().and_then(|card| card.scheme.clone())),
metadata: payment_method_metadata.map(Secret::new), metadata: payment_method_metadata.map(Secret::new),
payment_method_data: payment_method_data_encrypted.map(Into::into), payment_method_data: payment_method_data_encrypted.map(Into::into),
connector_mandate_details: Some(connector_mandate_details), connector_mandate_details: Some(connector_mandate_details),
@ -554,7 +559,7 @@ pub async fn skip_locker_call_and_migrate_payment_method(
.map_err(|error| logger::error!(?error, "Failed to set the payment method as default")); .map_err(|error| logger::error!(?error, "Failed to set the payment method as default"));
} }
Ok(services::api::ApplicationResponse::Json( Ok(services::api::ApplicationResponse::Json(
api::PaymentMethodResponse::foreign_from((card, response)), api::PaymentMethodResponse::foreign_from((Some(card), response)),
)) ))
} }

View File

@ -5085,6 +5085,7 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
key_store: &domain::MerchantKeyStore, key_store: &domain::MerchantKeyStore,
connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference, connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference,
merchant_id: &str, merchant_id: &str,
card_network: Option<api_enums::CardNetwork>,
) -> CustomResult<(), errors::ApiErrorResponse> { ) -> CustomResult<(), errors::ApiErrorResponse> {
let db = &*state.store; let db = &*state.store;
let merchant_connector_account_list = db let merchant_connector_account_list = db
@ -5097,20 +5098,49 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
.await .await
.to_not_found_response(errors::ApiErrorResponse::InternalServerError)?; .to_not_found_response(errors::ApiErrorResponse::InternalServerError)?;
let merchant_connector_ids: Vec<String> = merchant_connector_account_list let merchant_connector_account_details_hash_map: std::collections::HashMap<
String,
domain::MerchantConnectorAccount,
> = merchant_connector_account_list
.iter() .iter()
.map(|merchant_connector_account| merchant_connector_account.merchant_connector_id.clone()) .map(|merchant_connector_account| {
(
merchant_connector_account.merchant_connector_id.clone(),
merchant_connector_account.clone(),
)
})
.collect(); .collect();
for merchant_connector_id in connector_mandate_details.0.keys() { for (migrating_merchant_connector_id, migrating_connector_mandate_details) in
if !merchant_connector_ids.contains(merchant_connector_id) { connector_mandate_details.0.clone()
Err(errors::ApiErrorResponse::InvalidDataValue { {
field_name: "merchant_connector_id", match (card_network.clone() ,merchant_connector_account_details_hash_map.get(&migrating_merchant_connector_id)) {
}) (Some(enums::CardNetwork::Discover),Some(merchant_connector_account_details)) => if let ("cybersource", None) = (
.attach_printable_lazy(|| { merchant_connector_account_details.connector_name.as_str(),
format!("{merchant_connector_id} invalid merchant connector id in connector_mandate_details") migrating_connector_mandate_details
})? .original_payment_authorized_amount
} .zip(
migrating_connector_mandate_details
.original_payment_authorized_currency,
),
) {
Err(errors::ApiErrorResponse::MissingRequiredFields {
field_names: vec![
"original_payment_authorized_currency",
"original_payment_authorized_amount",
],
}).attach_printable(format!("Invalid connector_mandate_details provided for connector {migrating_merchant_connector_id}"))?
},
(_, Some(_)) => (),
(_, None) => {
Err(errors::ApiErrorResponse::InvalidDataValue {
field_name: "merchant_connector_id",
})
.attach_printable_lazy(|| {
format!("{migrating_merchant_connector_id} invalid merchant connector id in connector_mandate_details")
})?
},
}
} }
Ok(()) Ok(())
} }