mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-10-29 17:19:15 +08:00
feat(authentication): create api for update profile acquirer (#8307)
Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
@ -285,6 +285,8 @@ pub enum StripeErrorCode {
|
||||
PlatformBadRequest,
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Platform Unauthorized Request")]
|
||||
PlatformUnauthorizedRequest,
|
||||
#[error(error_type = StripeErrorType::HyperswitchError, code = "", message = "Profile Acquirer not found")]
|
||||
ProfileAcquirerNotFound,
|
||||
// [#216]: https://github.com/juspay/hyperswitch/issues/216
|
||||
// Implement the remaining stripe error codes
|
||||
|
||||
@ -688,6 +690,9 @@ impl From<errors::ApiErrorResponse> for StripeErrorCode {
|
||||
}
|
||||
errors::ApiErrorResponse::PlatformAccountAuthNotSupported => Self::PlatformBadRequest,
|
||||
errors::ApiErrorResponse::InvalidPlatformOperation => Self::PlatformUnauthorizedRequest,
|
||||
errors::ApiErrorResponse::ProfileAcquirerNotFound { .. } => {
|
||||
Self::ProfileAcquirerNotFound
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -782,6 +787,7 @@ impl actix_web::ResponseError for StripeErrorCode {
|
||||
StatusCode::from_u16(*code).unwrap_or(StatusCode::OK)
|
||||
}
|
||||
Self::LockTimeout => StatusCode::LOCKED,
|
||||
Self::ProfileAcquirerNotFound => StatusCode::NOT_FOUND,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -96,19 +96,127 @@ pub async fn create_profile_acquirer(
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get updated acquirer config")?;
|
||||
|
||||
let response = profile_acquirer::ProfileAcquirerResponse {
|
||||
let response = profile_acquirer::ProfileAcquirerResponse::from((
|
||||
profile_acquirer_id,
|
||||
profile_id: request.profile_id.clone(),
|
||||
acquirer_assigned_merchant_id: updated_acquire_details
|
||||
.acquirer_assigned_merchant_id
|
||||
.clone(),
|
||||
merchant_name: updated_acquire_details.merchant_name.clone(),
|
||||
merchant_country_code: updated_acquire_details.merchant_country_code,
|
||||
network: updated_acquire_details.network.clone(),
|
||||
acquirer_bin: updated_acquire_details.acquirer_bin.clone(),
|
||||
acquirer_ica: updated_acquire_details.acquirer_ica.clone(),
|
||||
acquirer_fraud_rate: updated_acquire_details.acquirer_fraud_rate,
|
||||
};
|
||||
&request.profile_id,
|
||||
updated_acquire_details,
|
||||
));
|
||||
|
||||
Ok(api::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "v1"))]
|
||||
pub async fn update_profile_acquirer_config(
|
||||
state: SessionState,
|
||||
profile_id: common_utils::id_type::ProfileId,
|
||||
profile_acquirer_id: common_utils::id_type::ProfileAcquirerId,
|
||||
request: profile_acquirer::ProfileAcquirerUpdate,
|
||||
merchant_context: domain::MerchantContext,
|
||||
) -> RouterResponse<profile_acquirer::ProfileAcquirerResponse> {
|
||||
let db = state.store.as_ref();
|
||||
let key_manager_state = (&state).into();
|
||||
let merchant_key_store = merchant_context.get_merchant_key_store();
|
||||
|
||||
let mut business_profile = db
|
||||
.find_business_profile_by_profile_id(&key_manager_state, merchant_key_store, &profile_id)
|
||||
.await
|
||||
.to_not_found_response(errors::ApiErrorResponse::ProfileNotFound {
|
||||
id: profile_id.get_string_repr().to_owned(),
|
||||
})?;
|
||||
|
||||
let acquirer_config_map = business_profile
|
||||
.acquirer_config_map
|
||||
.as_mut()
|
||||
.ok_or(errors::ApiErrorResponse::ProfileAcquirerNotFound {
|
||||
profile_id: profile_id.get_string_repr().to_owned(),
|
||||
profile_acquirer_id: profile_acquirer_id.get_string_repr().to_owned(),
|
||||
})
|
||||
.attach_printable("no acquirer config found in business profile")?;
|
||||
|
||||
let mut potential_updated_config = acquirer_config_map
|
||||
.0
|
||||
.get(&profile_acquirer_id)
|
||||
.ok_or_else(|| errors::ApiErrorResponse::ProfileAcquirerNotFound {
|
||||
profile_id: profile_id.get_string_repr().to_owned(),
|
||||
profile_acquirer_id: profile_acquirer_id.get_string_repr().to_owned(),
|
||||
})?
|
||||
.clone();
|
||||
|
||||
// updating value in existing acquirer config
|
||||
request
|
||||
.acquirer_assigned_merchant_id
|
||||
.map(|val| potential_updated_config.acquirer_assigned_merchant_id = val);
|
||||
request
|
||||
.merchant_name
|
||||
.map(|val| potential_updated_config.merchant_name = val);
|
||||
request
|
||||
.merchant_country_code
|
||||
.map(|val| potential_updated_config.merchant_country_code = val);
|
||||
request
|
||||
.network
|
||||
.map(|val| potential_updated_config.network = val);
|
||||
request
|
||||
.acquirer_bin
|
||||
.map(|val| potential_updated_config.acquirer_bin = val);
|
||||
request
|
||||
.acquirer_ica
|
||||
.map(|val| potential_updated_config.acquirer_ica = Some(val.clone()));
|
||||
request
|
||||
.acquirer_fraud_rate
|
||||
.map(|val| potential_updated_config.acquirer_fraud_rate = val);
|
||||
|
||||
// checking for duplicates in the acquirerConfigMap
|
||||
match acquirer_config_map
|
||||
.0
|
||||
.iter()
|
||||
.find(|(_existing_id, existing_config_val_ref)| {
|
||||
**existing_config_val_ref == potential_updated_config
|
||||
}) {
|
||||
Some((conflicting_id_of_found_item, _)) => {
|
||||
Err(error_stack::report!(errors::ApiErrorResponse::GenericDuplicateError {
|
||||
message: format!(
|
||||
"Duplicate acquirer configuration. This configuration already exists for profile_acquirer_id '{}' under profile_id '{}'.",
|
||||
conflicting_id_of_found_item.get_string_repr(),
|
||||
profile_id.get_string_repr()
|
||||
),
|
||||
}))
|
||||
}
|
||||
None => Ok(()),
|
||||
}?;
|
||||
|
||||
acquirer_config_map
|
||||
.0
|
||||
.insert(profile_acquirer_id.clone(), potential_updated_config);
|
||||
|
||||
let updated_map_for_db_update = business_profile.acquirer_config_map.clone();
|
||||
|
||||
let profile_update = domain::ProfileUpdate::AcquirerConfigMapUpdate {
|
||||
acquirer_config_map: updated_map_for_db_update,
|
||||
};
|
||||
|
||||
let updated_business_profile = db
|
||||
.update_profile_by_profile_id(
|
||||
&key_manager_state,
|
||||
merchant_key_store,
|
||||
business_profile,
|
||||
profile_update,
|
||||
)
|
||||
.await
|
||||
.change_context(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to update business profile with updated acquirer config")?;
|
||||
|
||||
let final_acquirer_details = updated_business_profile
|
||||
.acquirer_config_map
|
||||
.as_ref()
|
||||
.and_then(|configs_wrapper| configs_wrapper.0.get(&profile_acquirer_id))
|
||||
.ok_or(errors::ApiErrorResponse::InternalServerError)
|
||||
.attach_printable("Failed to get updated acquirer config after DB update")?;
|
||||
|
||||
let response = profile_acquirer::ProfileAcquirerResponse::from((
|
||||
profile_acquirer_id,
|
||||
&profile_id,
|
||||
final_acquirer_details,
|
||||
));
|
||||
|
||||
Ok(api::ApplicationResponse::Json(response))
|
||||
}
|
||||
|
||||
@ -2634,5 +2634,9 @@ impl ProfileAcquirer {
|
||||
.service(
|
||||
web::resource("").route(web::post().to(profile_acquirer::create_profile_acquirer)),
|
||||
)
|
||||
.service(
|
||||
web::resource("/{profile_id}/{profile_acquirer_id}")
|
||||
.route(web::post().to(profile_acquirer::profile_acquirer_update)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -346,7 +346,7 @@ impl From<Flow> for ApiIdentifier {
|
||||
Flow::RevenueRecoveryRetrieve => Self::ProcessTracker,
|
||||
Flow::Proxy => Self::Proxy,
|
||||
|
||||
Flow::ProfileAcquirerCreate => Self::ProfileAcquirer,
|
||||
Flow::ProfileAcquirerCreate | Flow::ProfileAcquirerUpdate => Self::ProfileAcquirer,
|
||||
Flow::ThreeDsDecisionRuleExecute => Self::ThreeDsDecisionRule,
|
||||
Flow::TokenizationCreate | Flow::TokenizationRetrieve => Self::GenericTokenization,
|
||||
}
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use actix_web::{web, HttpRequest, HttpResponse};
|
||||
use api_models::profile_acquirer::ProfileAcquirerCreate;
|
||||
use api_models::profile_acquirer::{ProfileAcquirerCreate, ProfileAcquirerUpdate};
|
||||
use router_env::{instrument, tracing, Flow};
|
||||
|
||||
use super::app::AppState;
|
||||
@ -48,3 +48,50 @@ pub async fn create_profile_acquirer(
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "olap", feature = "v1"))]
|
||||
#[instrument(skip_all, fields(flow = ?Flow::ProfileAcquirerUpdate))]
|
||||
pub async fn profile_acquirer_update(
|
||||
state: web::Data<AppState>,
|
||||
req: HttpRequest,
|
||||
path: web::Path<(
|
||||
common_utils::id_type::ProfileId,
|
||||
common_utils::id_type::ProfileAcquirerId,
|
||||
)>,
|
||||
json_payload: web::Json<ProfileAcquirerUpdate>,
|
||||
) -> HttpResponse {
|
||||
let flow = Flow::ProfileAcquirerUpdate;
|
||||
let (profile_id, profile_acquirer_id) = path.into_inner();
|
||||
let payload = json_payload.into_inner();
|
||||
|
||||
Box::pin(api::server_wrap(
|
||||
flow,
|
||||
state,
|
||||
&req,
|
||||
payload,
|
||||
|state: super::SessionState, auth_data, req, _| {
|
||||
let merchant_context = domain::MerchantContext::NormalMerchant(Box::new(
|
||||
domain::Context(auth_data.merchant_account, auth_data.key_store),
|
||||
));
|
||||
crate::core::profile_acquirer::update_profile_acquirer_config(
|
||||
state,
|
||||
profile_id.clone(),
|
||||
profile_acquirer_id.clone(),
|
||||
req,
|
||||
merchant_context.clone(),
|
||||
)
|
||||
},
|
||||
auth::auth_type(
|
||||
&auth::HeaderAuth(auth::ApiKeyAuth {
|
||||
is_connected_allowed: false,
|
||||
is_platform_allowed: true,
|
||||
}),
|
||||
&auth::JWTAuth {
|
||||
permission: Permission::ProfileAccountWrite,
|
||||
},
|
||||
req.headers(),
|
||||
),
|
||||
api_locking::LockAction::NotApplicable,
|
||||
))
|
||||
.await
|
||||
}
|
||||
|
||||
@ -36,6 +36,32 @@ use crate::{
|
||||
utils,
|
||||
};
|
||||
|
||||
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
|
||||
pub struct ProfileAcquirerConfigs {
|
||||
pub acquirer_config_map: Option<common_types::domain::AcquirerConfigMap>,
|
||||
pub profile_id: common_utils::id_type::ProfileId,
|
||||
}
|
||||
|
||||
impl From<ProfileAcquirerConfigs>
|
||||
for Option<Vec<api_models::profile_acquirer::ProfileAcquirerResponse>>
|
||||
{
|
||||
fn from(item: ProfileAcquirerConfigs) -> Self {
|
||||
item.acquirer_config_map.map(|config_map_val| {
|
||||
let mut vec: Vec<_> = config_map_val.0.into_iter().collect();
|
||||
vec.sort_by_key(|k| k.0.clone());
|
||||
vec.into_iter()
|
||||
.map(|(profile_acquirer_id, acquirer_config)| {
|
||||
api_models::profile_acquirer::ProfileAcquirerResponse::from((
|
||||
profile_acquirer_id,
|
||||
&item.profile_id,
|
||||
&acquirer_config,
|
||||
))
|
||||
})
|
||||
.collect::<Vec<api_models::profile_acquirer::ProfileAcquirerResponse>>()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ForeignFrom<diesel_models::organization::Organization> for OrganizationResponse {
|
||||
fn foreign_from(org: diesel_models::organization::Organization) -> Self {
|
||||
Self {
|
||||
@ -148,7 +174,7 @@ impl ForeignTryFrom<domain::Profile> for ProfileResponse {
|
||||
|
||||
Ok(Self {
|
||||
merchant_id: item.merchant_id,
|
||||
profile_id,
|
||||
profile_id: profile_id.clone(),
|
||||
profile_name: item.profile_name,
|
||||
return_url: item.return_url,
|
||||
enable_payment_response_hash: item.enable_payment_response_hash,
|
||||
@ -197,7 +223,11 @@ impl ForeignTryFrom<domain::Profile> for ProfileResponse {
|
||||
is_debit_routing_enabled: Some(item.is_debit_routing_enabled),
|
||||
merchant_business_country: item.merchant_business_country,
|
||||
is_pre_network_tokenization_enabled: item.is_pre_network_tokenization_enabled,
|
||||
acquirer_configs: item.acquirer_config_map,
|
||||
acquirer_configs: ProfileAcquirerConfigs {
|
||||
acquirer_config_map: item.acquirer_config_map.clone(),
|
||||
profile_id: profile_id.clone(),
|
||||
}
|
||||
.into(),
|
||||
is_iframe_redirection_enabled: item.is_iframe_redirection_enabled,
|
||||
merchant_category_code: item.merchant_category_code,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user