Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions config/superposition_seed.toml
Original file line number Diff line number Diff line change
Expand Up @@ -98,12 +98,6 @@ schema = { type = "boolean" }
description = "Whether client session validation is enabled for payments"
change_reason = "Initial setup"

[default-configs.fingerprint_secret]
value = ""
schema = { type = "string" }
description = "Fingerprint secret used for payment card fingerprinting per merchant"
change_reason = "Initial setup"

[default-configs.should_call_gsm]
value = false
schema = { type = "boolean" }
Expand Down
5 changes: 0 additions & 5 deletions crates/common_utils/src/id_type/merchant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -130,11 +130,6 @@ impl MerchantId {
format!("pre_routing_disabled_pm_pmt_for_{}", self.get_string_repr())
}

/// get_merchant_fingerprint_secret_key
pub fn get_merchant_fingerprint_secret_key(&self) -> String {
format!("fingerprint_secret_{}", self.get_string_repr())
}

/// get_surcharge_dsk_key
pub fn get_surcharge_dsk_key(&self) -> String {
format!("surcharge_dsl_{}", self.get_string_repr())
Expand Down
9 changes: 9 additions & 0 deletions crates/diesel_models/src/merchant_account.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use common_utils::{encryption::Encryption, pii};
use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
use hyperswitch_masking::Secret;

use crate::enums as storage_enums;
#[cfg(feature = "v1")]
Expand Down Expand Up @@ -56,6 +57,7 @@ pub struct MerchantAccount {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: Option<common_enums::MerchantAccountType>,
pub network_tokenization_credentials: Option<Encryption>,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -92,6 +94,7 @@ pub struct MerchantAccountSetter {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub network_tokenization_credentials: Option<Encryption>,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -131,6 +134,7 @@ impl From<MerchantAccountSetter> for MerchantAccount {
product_type: item.product_type,
merchant_account_type: Some(item.merchant_account_type),
network_tokenization_credentials: item.network_tokenization_credentials,
fingerprint_secret: item.fingerprint_secret,
}
}
}
Expand Down Expand Up @@ -166,6 +170,7 @@ pub struct MerchantAccount {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: Option<common_enums::MerchantAccountType>,
pub network_tokenization_credentials: Option<Encryption>,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v2")]
Expand All @@ -187,6 +192,7 @@ impl From<MerchantAccountSetter> for MerchantAccount {
product_type: item.product_type,
merchant_account_type: Some(item.merchant_account_type),
network_tokenization_credentials: None, // need to check if we can have this column in v2
fingerprint_secret: item.fingerprint_secret,
}
}
}
Expand All @@ -207,6 +213,7 @@ pub struct MerchantAccountSetter {
pub is_platform_account: bool,
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub fingerprint_secret: Option<Secret<String>>,
}

impl MerchantAccount {
Expand Down Expand Up @@ -259,6 +266,7 @@ pub struct MerchantAccountNew {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub network_tokenization_credentials: Option<Encryption>,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v2")]
Expand All @@ -279,6 +287,7 @@ pub struct MerchantAccountNew {
pub is_platform_account: bool,
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v2")]
Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1032,6 +1032,8 @@ diesel::table! {
#[max_length = 64]
merchant_account_type -> Nullable<Varchar>,
network_tokenization_credentials -> Nullable<Bytea>,
#[max_length = 128]
fingerprint_secret -> Nullable<Varchar>,
}
}

Expand Down
2 changes: 2 additions & 0 deletions crates/diesel_models/src/schema_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1020,6 +1020,8 @@ diesel::table! {
#[max_length = 64]
merchant_account_type -> Nullable<Varchar>,
network_tokenization_credentials -> Nullable<Bytea>,
#[max_length = 128]
fingerprint_secret -> Nullable<Varchar>,
}
}

Expand Down
13 changes: 13 additions & 0 deletions crates/hyperswitch_domain_models/src/merchant_account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ pub struct MerchantAccount {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub network_tokenization_credentials: OptionalEncryptableValue,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -93,6 +94,7 @@ pub struct MerchantAccountSetter {
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub network_tokenization_credentials: OptionalEncryptableValue,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v1")]
Expand Down Expand Up @@ -131,6 +133,7 @@ impl From<MerchantAccountSetter> for MerchantAccount {
product_type: item.product_type,
merchant_account_type: item.merchant_account_type,
network_tokenization_credentials: item.network_tokenization_credentials,
fingerprint_secret: item.fingerprint_secret,
}
}
}
Expand All @@ -153,6 +156,7 @@ pub struct MerchantAccountSetter {
pub version: common_enums::ApiVersion,
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub fingerprint_secret: Option<Secret<String>>,
}

#[cfg(feature = "v2")]
Expand All @@ -173,6 +177,7 @@ impl From<MerchantAccountSetter> for MerchantAccount {
version,
product_type,
merchant_account_type,
fingerprint_secret,
} = item;
Self {
id,
Expand All @@ -189,6 +194,7 @@ impl From<MerchantAccountSetter> for MerchantAccount {
version,
product_type,
merchant_account_type,
fingerprint_secret,
}
}
}
Expand All @@ -210,6 +216,7 @@ pub struct MerchantAccount {
pub version: common_enums::ApiVersion,
pub product_type: Option<common_enums::MerchantProductType>,
pub merchant_account_type: common_enums::MerchantAccountType,
pub fingerprint_secret: Option<Secret<String>>,
}

impl MerchantAccount {
Expand Down Expand Up @@ -585,6 +592,7 @@ impl Conversion for MerchantAccount {
is_platform_account: self.is_platform_account,
product_type: self.product_type,
merchant_account_type: self.merchant_account_type,
fingerprint_secret: self.fingerprint_secret,
};

Ok(diesel_models::MerchantAccount::from(setter))
Expand Down Expand Up @@ -648,6 +656,7 @@ impl Conversion for MerchantAccount {
version: item.version,
product_type: item.product_type,
merchant_account_type: item.merchant_account_type.unwrap_or_default(),
fingerprint_secret: item.fingerprint_secret,
})
}
.await
Expand Down Expand Up @@ -675,6 +684,7 @@ impl Conversion for MerchantAccount {
.product_type
.or(Some(common_enums::MerchantProductType::Orchestration)),
merchant_account_type: self.merchant_account_type,
fingerprint_secret: self.fingerprint_secret,
})
}
}
Expand Down Expand Up @@ -720,6 +730,7 @@ impl Conversion for MerchantAccount {
network_tokenization_credentials: self
.network_tokenization_credentials
.map(|credentials| credentials.into()),
fingerprint_secret: self.fingerprint_secret,
};

Ok(diesel_models::MerchantAccount::from(setter))
Expand Down Expand Up @@ -814,6 +825,7 @@ impl Conversion for MerchantAccount {
.and_then(|val| val.try_into_optionaloperation())
})
.await?,
fingerprint_secret: item.fingerprint_secret,
})
}
.await
Expand Down Expand Up @@ -862,6 +874,7 @@ impl Conversion for MerchantAccount {
network_tokenization_credentials: self
.network_tokenization_credentials
.map(|credentials| credentials.into()),
fingerprint_secret: self.fingerprint_secret,
})
}
}
Expand Down
2 changes: 0 additions & 2 deletions crates/router/src/consts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -369,8 +369,6 @@ pub mod superposition {
pub const REQUIRES_CVV: &str = "requires_cvv";
/// implicit customer update configuration key
pub const IMPLICIT_CUSTOMER_UPDATE: &str = "implicit_customer_update";
/// Fingerprint secret configuration key
pub const FINGERPRINT_SECRET: &str = "fingerprint_secret";
/// Poll config for external 3DS authentication key
pub const POLL_CONFIG_EXTERNAL_THREE_DS: &str = "poll_config_external_three_ds";
/// Outgoing webhook retry process tracker mapping key
Expand Down
33 changes: 8 additions & 25 deletions crates/router/src/core/admin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ use crate::types::transformers::ForeignFrom;
use crate::{
consts,
core::{
configs::dimension_state,
connector_validation::ConnectorAuthTypeAndMetadataValidation,
disputes,
encryption::transfer_encryption_key,
Expand Down Expand Up @@ -74,21 +73,6 @@ pub fn create_merchant_publishable_key() -> String {
)
}

/// Insert merchant configs using Superposition for fingerprint_secret
pub async fn insert_merchant_configs_with_superposition(
state: &SessionState,
dimensions: &dimension_state::DimensionsWithProcessorMerchantId,
) -> RouterResult<()> {
let fingerprint_secret = utils::generate_id(consts::FINGERPRINT_SECRET_LENGTH, "fs");

dimensions
.set_fingerprint_secret(state.superposition_service.as_ref(), &fingerprint_secret)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Failed to create fingerprint_secret in Superposition")?;
Ok(())
}

#[cfg(feature = "olap")]
fn add_publishable_key_to_decision_service(state: &SessionState, platform: &domain::Platform) {
let state = state.clone();
Expand Down Expand Up @@ -397,17 +381,8 @@ pub async fn create_merchant_account(
None,
);

let dimensions = dimension_state::Dimensions::new()
.with_processor_merchant_id(platform.get_processor().get_processor_merchant_id());

add_publishable_key_to_decision_service(&state, &platform);

Box::pin(insert_merchant_configs_with_superposition(
&state,
&dimensions,
))
.await?;

Ok(service_api::ApplicationResponse::Json(
api::MerchantAccountResponse::foreign_try_from(merchant_account)
.change_context(errors::ApiErrorResponse::InternalServerError)
Expand Down Expand Up @@ -645,6 +620,10 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
product_type: self.product_type,
merchant_account_type,
network_tokenization_credentials,
fingerprint_secret: Some(Secret::new(utils::generate_id(
consts::FINGERPRINT_SECRET_LENGTH,
"fs",
))),
},
)
}
Expand Down Expand Up @@ -941,6 +920,10 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate {
version: common_types::consts::API_VERSION,
product_type: self.product_type,
merchant_account_type,
fingerprint_secret: Some(Secret::new(utils::generate_id(
consts::FINGERPRINT_SECRET_LENGTH,
"fs",
))),
}),
)
}
Expand Down
41 changes: 14 additions & 27 deletions crates/router/src/core/blocklist/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use common_enums::{BlockReason, MerchantDecision};
use common_utils::errors::CustomResult;
use diesel_models::{business_profile::CardBlockingConfig, configs};
use error_stack::ResultExt;
use hyperswitch_masking::StrongSecret;
use hyperswitch_masking::{PeekInterface, StrongSecret};

use super::{errors, transformers::generate_fingerprint, SessionState};
use crate::{
Expand Down Expand Up @@ -231,24 +231,15 @@ pub async fn insert_entry_into_blocklist(
Ok(blocklist_entry.foreign_into())
}

pub async fn get_merchant_fingerprint_secret(
state: &SessionState,
dimensions: &dimension_state::DimensionsWithProcessorMerchantId,
pub fn get_merchant_fingerprint_secret(
merchant_account: &domain::MerchantAccount,
) -> RouterResult<String> {
// Fetch from Superposition only
let secret = dimensions
.get_fingerprint_secret(
&*state.store,
state.superposition_service.as_ref(),
None, // No targeting key needed for merchant-level config
)
.await;

match secret.is_empty() {
false => Ok(secret),
true => Err(errors::ApiErrorResponse::InternalServerError)
.attach_printable("fingerprint_secret not found in Superposition for merchant"),
}
merchant_account

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should have superposition as a fallback for now if the value is not found. We already have several fingerprint_secrets stored in superposition. We will need to migrate them to the merchant table before we can deploy this change.

This is easy enough for us, but this will also impact the self hosted merchants who are using superposition. If they miss the migration before the new deployment they might start experiencing transaction failures.

Ideally migrations like this should be automatically done in the background on application startup or as a background job and should be transparent to the self hosted merchant.

.fingerprint_secret
.as_ref()
.map(|secret| secret.peek().clone())
.ok_or(errors::ApiErrorResponse::InternalServerError)
.attach_printable("fingerprint_secret not found in merchant account")
}

async fn duplicate_check_insert_bin(
Expand Down Expand Up @@ -317,15 +308,12 @@ async fn delete_card_bin_blocklist_entry(
pub async fn should_payment_be_blocked(
state: &SessionState,
processor: &domain::Processor,
dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
payment_method_data: &Option<domain::EligibilityPaymentMethodData>,
business_profile: &domain::Profile,
) -> CustomResult<Option<BlockReason>, errors::ApiErrorResponse> {
let db = &state.store;
let processor_merchant_id = processor.get_account().get_id();
let processor_dimensions = dimensions.without_provider_merchant_id();
let merchant_fingerprint_secret =
get_merchant_fingerprint_secret(state, &processor_dimensions).await?;
let merchant_fingerprint_secret = get_merchant_fingerprint_secret(processor.get_account())?;

// Hashed Fingerprint to check whether or not this payment should be blocked.
let card_number_fingerprint =
Expand Down Expand Up @@ -432,7 +420,7 @@ pub async fn should_payment_be_blocked(
pub async fn validate_data_for_blocklist<F>(
state: &SessionState,
processor: &domain::Processor,
dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
_dimensions: &dimension_state::DimensionsWithProcessorAndProviderMerchantId,
payment_data: &mut PaymentData<F>,
business_profile: &domain::Profile,
) -> CustomResult<bool, errors::ApiErrorResponse>
Expand All @@ -443,7 +431,6 @@ where
let block_reason = should_payment_be_blocked(
state,
processor,
dimensions,
&payment_data
.payment_method_data
.clone()
Expand Down Expand Up @@ -500,7 +487,7 @@ where
} else {
payment_data.payment_attempt.fingerprint_id = generate_payment_fingerprint(
state,
&dimensions.without_provider_merchant_id(),
processor.get_account(),
payment_data.payment_method_data.clone(),
)
.await?;
Expand Down Expand Up @@ -606,10 +593,10 @@ pub async fn should_payment_be_blocked_by_profile_config(

pub async fn generate_payment_fingerprint(
state: &SessionState,
dimensions: &dimension_state::DimensionsWithProcessorMerchantId,
merchant_account: &domain::MerchantAccount,
payment_method_data: Option<domain::PaymentMethodData>,
) -> CustomResult<Option<String>, errors::ApiErrorResponse> {
let merchant_fingerprint_secret = get_merchant_fingerprint_secret(state, dimensions).await?;
let merchant_fingerprint_secret = get_merchant_fingerprint_secret(merchant_account)?;

Ok(
if let Some(domain::PaymentMethodData::Card(card)) = payment_method_data.as_ref() {
Expand Down
Loading
Loading