mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-03 05:17:02 +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:
@ -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 {
|
||||||
|
|||||||
@ -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)),
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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(())
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user