mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 04:04:43 +08:00
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:
@ -278,17 +278,30 @@ pub async fn migrate_payment_method(
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
let mut req = req;
|
||||
let card_details = req.card.as_ref().get_required_value("card")?;
|
||||
|
||||
let card_number_validation_result =
|
||||
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 {
|
||||
helpers::validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
&state,
|
||||
key_store,
|
||||
connector_mandate_details,
|
||||
merchant_id,
|
||||
card_bin_details.card_network.clone(),
|
||||
)
|
||||
.await?;
|
||||
};
|
||||
@ -316,18 +329,130 @@ pub async fn migrate_payment_method(
|
||||
merchant_id.into(),
|
||||
key_store,
|
||||
merchant_account,
|
||||
card_bin_details.clone(),
|
||||
)
|
||||
.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(
|
||||
state: routes::SessionState,
|
||||
req: &api::PaymentMethodMigrate,
|
||||
merchant_id: String,
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
merchant_account: &domain::MerchantAccount,
|
||||
card: api_models::payment_methods::CardDetailFromLocker,
|
||||
) -> errors::RouterResponse<api::PaymentMethodResponse> {
|
||||
let db = &*state.store;
|
||||
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
|
||||
.to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?;
|
||||
|
||||
let card = if let Some(card_details) = &req.card {
|
||||
helpers::validate_card_expiry(&card_details.card_exp_month, &card_details.card_exp_year)?;
|
||||
let card_number = card_details.card_number.clone();
|
||||
let payment_method_card_details =
|
||||
PaymentMethodsData::Card(CardDetailsPaymentMethod::from(card.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(),
|
||||
})?;
|
||||
|
||||
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))
|
||||
let payment_method_data_encrypted: Option<Encryptable<Secret<serde_json::Value>>> = Some(
|
||||
create_encrypted_data(&state, key_store, payment_method_card_details)
|
||||
.await
|
||||
.transpose()
|
||||
.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> =
|
||||
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_type: req.payment_method_type,
|
||||
payment_method_issuer: req.payment_method_issuer.clone(),
|
||||
scheme: req
|
||||
.card_network
|
||||
.clone()
|
||||
.or(card.clone().and_then(|card| card.scheme.clone())),
|
||||
scheme: req.card_network.clone().or(card.scheme.clone()),
|
||||
metadata: payment_method_metadata.map(Secret::new),
|
||||
payment_method_data: payment_method_data_encrypted.map(Into::into),
|
||||
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"));
|
||||
}
|
||||
Ok(services::api::ApplicationResponse::Json(
|
||||
api::PaymentMethodResponse::foreign_from((card, response)),
|
||||
api::PaymentMethodResponse::foreign_from((Some(card), response)),
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@ -5085,6 +5085,7 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
key_store: &domain::MerchantKeyStore,
|
||||
connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference,
|
||||
merchant_id: &str,
|
||||
card_network: Option<api_enums::CardNetwork>,
|
||||
) -> CustomResult<(), errors::ApiErrorResponse> {
|
||||
let db = &*state.store;
|
||||
let merchant_connector_account_list = db
|
||||
@ -5097,20 +5098,49 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details(
|
||||
.await
|
||||
.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()
|
||||
.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();
|
||||
|
||||
for merchant_connector_id in connector_mandate_details.0.keys() {
|
||||
if !merchant_connector_ids.contains(merchant_connector_id) {
|
||||
Err(errors::ApiErrorResponse::InvalidDataValue {
|
||||
field_name: "merchant_connector_id",
|
||||
})
|
||||
.attach_printable_lazy(|| {
|
||||
format!("{merchant_connector_id} invalid merchant connector id in connector_mandate_details")
|
||||
})?
|
||||
}
|
||||
for (migrating_merchant_connector_id, migrating_connector_mandate_details) in
|
||||
connector_mandate_details.0.clone()
|
||||
{
|
||||
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) = (
|
||||
merchant_connector_account_details.connector_name.as_str(),
|
||||
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(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user