feat(router): add merchantId authentication for Payments v2 (#8239)

Co-authored-by: Aishwariyaa Anand <aishwariyaa.anand@Aishwariyaa-Anand-C3PGW02T6Y.local>
This commit is contained in:
Aishwariyaa Anand
2025-06-06 13:55:03 +05:30
committed by GitHub
parent 4fa7b12400
commit 45d4ebfde3
8 changed files with 56 additions and 19 deletions

View File

@ -162,6 +162,11 @@ max_age = 365 # Max age of a refund in days.
outgoing_enabled = true
redis_lock_expiry_seconds = 180
# Controls whether merchant ID authentication is enabled.
# When enabled, payment endpoints will accept and require a x-merchant-id header in the request.
[merchant_id_auth]
merchant_id_auth_enabled = false
# Validity of an Ephemeral Key in Hours
[eph_key]
validity = 1

View File

@ -155,6 +155,11 @@ bg_metrics_collection_interval_in_secs = 15 # Interval for collecting
delay_between_retries_in_milliseconds = 500 # Delay between retries in milliseconds
redis_lock_expiry_seconds = 180 # Seconds before the redis lock expires
# Controls whether merchant ID authentication is enabled.
# When enabled, payment endpoints will accept and require a x-merchant-id header in the request.
[merchant_id_auth]
merchant_id_auth_enabled = false
# Main SQL data store credentials
[master_database]
username = "db_user" # DB Username

View File

@ -197,6 +197,9 @@ max_age = 365
outgoing_enabled = true
redis_lock_expiry_seconds = 180 # 3 * 60 seconds
[merchant_id_auth]
merchant_id_auth_enabled = false
[eph_key]
validity = 1

View File

@ -893,6 +893,9 @@ delay_between_retries_in_milliseconds = 500
outgoing_enabled = true
redis_lock_expiry_seconds = 180 # 3 * 60 seconds
[merchant_id_auth]
merchant_id_auth_enabled = false
[events.kafka]
brokers = ["localhost:9092"]
fraud_check_analytics_topic = "hyperswitch-fraud-check-events"

View File

@ -540,6 +540,7 @@ pub(crate) async fn fetch_raw_secrets(
revenue_recovery: conf.revenue_recovery,
debit_routing_config: conf.debit_routing_config,
clone_connector_allowlist: conf.clone_connector_allowlist,
merchant_id_auth: conf.merchant_id_auth,
infra_values: conf.infra_values,
}
}

View File

@ -158,6 +158,7 @@ pub struct Settings<S: SecretState> {
#[cfg(feature = "v2")]
pub revenue_recovery: revenue_recovery::RevenueRecoverySettings,
pub clone_connector_allowlist: Option<CloneConnectorAllowlistConfig>,
pub merchant_id_auth: MerchantIdAuthSettings,
#[serde(default)]
pub infra_values: Option<HashMap<String, String>>,
}
@ -817,6 +818,12 @@ pub struct DrainerSettings {
pub loop_interval: u32, // in milliseconds
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct MerchantIdAuthSettings {
pub merchant_id_auth_enabled: bool,
}
#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default)]
pub struct WebhooksSettings {

View File

@ -262,6 +262,27 @@ pub async fn payments_create_and_confirm_intent(
}
};
let auth_type = if state.conf.merchant_id_auth.merchant_id_auth_enabled {
&auth::MerchantIdAuth
} else {
match env::which() {
env::Env::Production => &auth::V2ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
_ => auth::auth_type(
&auth::V2ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
&auth::JWTAuth {
permission: Permission::ProfilePaymentWrite,
},
req.headers(),
),
}
};
Box::pin(api::server_wrap(
flow,
state,
@ -280,22 +301,7 @@ pub async fn payments_create_and_confirm_intent(
header_payload.clone(),
)
},
match env::which() {
env::Env::Production => &auth::V2ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
_ => auth::auth_type(
&auth::V2ApiKeyAuth {
is_connected_allowed: false,
is_platform_allowed: false,
},
&auth::JWTAuth {
permission: Permission::ProfilePaymentWrite,
},
req.headers(),
),
},
auth_type,
api_locking::LockAction::NotApplicable,
))
.await

View File

@ -2184,6 +2184,7 @@ where
}
#[derive(Debug)]
#[cfg(feature = "v1")]
pub struct MerchantIdAuth(pub id_type::MerchantId);
#[cfg(feature = "v1")]
@ -2233,6 +2234,10 @@ where
}
}
#[derive(Debug)]
#[cfg(feature = "v2")]
pub struct MerchantIdAuth;
#[cfg(feature = "v2")]
#[async_trait]
impl<A> AuthenticateAndFetch<AuthenticationData, A> for MerchantIdAuth
@ -2249,6 +2254,8 @@ where
}
let key_manager_state = &(&state.session_state()).into();
let merchant_id = HeaderMapStruct::new(request_headers)
.get_id_type_from_header::<id_type::MerchantId>(headers::X_MERCHANT_ID)?;
let profile_id =
get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)?
.get_required_value(headers::X_PROFILE_ID)?;
@ -2256,7 +2263,7 @@ where
.store()
.get_merchant_key_store_by_merchant_id(
key_manager_state,
&self.0,
&merchant_id,
&state.store().get_master_key().to_vec().into(),
)
.await
@ -2267,14 +2274,14 @@ where
.find_business_profile_by_merchant_id_profile_id(
key_manager_state,
&key_store,
&self.0,
&merchant_id,
&profile_id,
)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;
let merchant = state
.store()
.find_merchant_account_by_merchant_id(key_manager_state, &self.0, &key_store)
.find_merchant_account_by_merchant_id(key_manager_state, &merchant_id, &key_store)
.await
.to_not_found_response(errors::ApiErrorResponse::Unauthorized)?;