feat(business_profile_v2): business profile v2 create and retrieve endpoint (#5606)

Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
Co-authored-by: Sanchith Hegde <sanchith.hegde@juspay.in>
This commit is contained in:
Narayan Bhat
2024-08-20 14:50:58 +05:30
committed by GitHub
parent ac7d8c572c
commit 6e7b38a622
28 changed files with 756 additions and 248 deletions

View File

@ -3270,25 +3270,23 @@ pub fn get_frm_config_as_secret(
}
}
#[cfg(any(feature = "v1", feature = "v2"))]
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "business_profile_v2")
))]
pub async fn create_and_insert_business_profile(
state: &SessionState,
request: api::BusinessProfileCreate,
merchant_account: domain::MerchantAccount,
key_store: &domain::MerchantKeyStore,
) -> RouterResult<domain::BusinessProfile> {
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "merchant_account_v2")
))]
let business_profile_new =
admin::create_business_profile(state, merchant_account, request, key_store).await?;
#[cfg(all(feature = "v2", feature = "merchant_account_v2"))]
let business_profile_new = {
let _ = merchant_account;
admin::create_business_profile(state, request, key_store).await?
};
let business_profile_new = admin::create_business_profile_from_merchant_account(
state,
merchant_account,
request,
key_store,
)
.await?;
let profile_name = business_profile_new.profile_name.clone();
@ -3304,23 +3302,261 @@ pub async fn create_and_insert_business_profile(
.attach_printable("Failed to insert Business profile because of duplication error")
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(any(feature = "merchant_account_v2", feature = "business_profile_v2"))
))]
#[cfg(feature = "olap")]
#[async_trait::async_trait]
trait BusinessProfileCreateBridge {
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "business_profile_v2")
))]
async fn create_domain_model_from_request(
self,
state: &SessionState,
merchant_account: &domain::MerchantAccount,
key: &domain::MerchantKeyStore,
) -> RouterResult<domain::BusinessProfile>;
#[cfg(all(feature = "v2", feature = "business_profile_v2"))]
async fn create_domain_model_from_request(
self,
state: &SessionState,
key: &domain::MerchantKeyStore,
merchant_id: &id_type::MerchantId,
) -> RouterResult<domain::BusinessProfile>;
}
#[cfg(feature = "olap")]
#[async_trait::async_trait]
impl BusinessProfileCreateBridge for api::BusinessProfileCreate {
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "business_profile_v2")
))]
async fn create_domain_model_from_request(
self,
state: &SessionState,
merchant_account: &domain::MerchantAccount,
key_store: &domain::MerchantKeyStore,
) -> RouterResult<domain::BusinessProfile> {
use common_utils::ext_traits::AsyncExt;
if let Some(session_expiry) = &self.session_expiry {
helpers::validate_session_expiry(session_expiry.to_owned())?;
}
if let Some(intent_fulfillment_expiry) = &self.intent_fulfillment_time {
helpers::validate_intent_fulfillment_expiry(intent_fulfillment_expiry.to_owned())?;
}
if let Some(ref routing_algorithm) = self.routing_algorithm {
let _: api_models::routing::RoutingAlgorithm = routing_algorithm
.clone()
.parse_value("RoutingAlgorithm")
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "routing_algorithm",
})
.attach_printable("Invalid routing algorithm given")?;
}
// Generate a unique profile id
let profile_id = common_utils::generate_id_with_default_len("pro");
let profile_name = self.profile_name.unwrap_or("default".to_string());
let current_time = date_time::now();
let webhook_details = self.webhook_details.map(ForeignInto::foreign_into);
let payment_response_hash_key = self
.payment_response_hash_key
.or(merchant_account.payment_response_hash_key.clone())
.unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64));
let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into);
let outgoing_webhook_custom_http_headers = self
.outgoing_webhook_custom_http_headers
.async_map(|headers| cards::create_encrypted_data(state, key_store, headers))
.await
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt outgoing webhook custom HTTP headers")?;
let payout_link_config = self
.payout_link_config
.map(|payout_conf| match payout_conf.config.validate() {
Ok(_) => Ok(payout_conf.foreign_into()),
Err(e) => Err(error_stack::report!(
errors::ApiErrorResponse::InvalidRequestData {
message: e.to_string()
}
)),
})
.transpose()?;
Ok(domain::BusinessProfile {
profile_id,
merchant_id: merchant_account.get_id().clone(),
profile_name,
created_at: current_time,
modified_at: current_time,
return_url: self
.return_url
.map(|return_url| return_url.to_string())
.or(merchant_account.return_url.clone()),
enable_payment_response_hash: self
.enable_payment_response_hash
.unwrap_or(merchant_account.enable_payment_response_hash),
payment_response_hash_key: Some(payment_response_hash_key),
redirect_to_merchant_with_http_post: self
.redirect_to_merchant_with_http_post
.unwrap_or(merchant_account.redirect_to_merchant_with_http_post),
webhook_details: webhook_details.or(merchant_account.webhook_details.clone()),
metadata: self.metadata,
routing_algorithm: None,
intent_fulfillment_time: self
.intent_fulfillment_time
.map(i64::from)
.or(merchant_account.intent_fulfillment_time)
.or(Some(common_utils::consts::DEFAULT_INTENT_FULFILLMENT_TIME)),
frm_routing_algorithm: self
.frm_routing_algorithm
.or(merchant_account.frm_routing_algorithm.clone()),
#[cfg(feature = "payouts")]
payout_routing_algorithm: self
.payout_routing_algorithm
.or(merchant_account.payout_routing_algorithm.clone()),
#[cfg(not(feature = "payouts"))]
payout_routing_algorithm: None,
is_recon_enabled: merchant_account.is_recon_enabled,
applepay_verified_domains: self.applepay_verified_domains,
payment_link_config,
session_expiry: self
.session_expiry
.map(i64::from)
.or(Some(common_utils::consts::DEFAULT_SESSION_EXPIRY)),
authentication_connector_details: self
.authentication_connector_details
.map(ForeignInto::foreign_into),
payout_link_config,
is_connector_agnostic_mit_enabled: self.is_connector_agnostic_mit_enabled,
is_extended_card_info_enabled: None,
extended_card_info_config: None,
use_billing_as_payment_method_billing: self
.use_billing_as_payment_method_billing
.or(Some(true)),
collect_shipping_details_from_wallet_connector: self
.collect_shipping_details_from_wallet_connector
.or(Some(false)),
collect_billing_details_from_wallet_connector: self
.collect_billing_details_from_wallet_connector
.or(Some(false)),
outgoing_webhook_custom_http_headers: outgoing_webhook_custom_http_headers
.map(Into::into),
})
}
#[cfg(all(feature = "v2", feature = "business_profile_v2"))]
async fn create_domain_model_from_request(
self,
state: &SessionState,
key_store: &domain::MerchantKeyStore,
merchant_id: &id_type::MerchantId,
) -> RouterResult<domain::BusinessProfile> {
if let Some(session_expiry) = &self.session_expiry {
helpers::validate_session_expiry(session_expiry.to_owned())?;
}
// Generate a unique profile id
// TODO: the profile_id should be generated from the profile_name
let profile_id = common_utils::generate_id_with_default_len("pro");
let profile_name = self.profile_name;
let current_time = date_time::now();
let webhook_details = self.webhook_details.map(ForeignInto::foreign_into);
let payment_response_hash_key = self
.payment_response_hash_key
.unwrap_or(common_utils::crypto::generate_cryptographically_secure_random_string(64));
let payment_link_config = self.payment_link_config.map(ForeignInto::foreign_into);
let outgoing_webhook_custom_http_headers = self
.outgoing_webhook_custom_http_headers
.async_map(|headers| cards::create_encrypted_data(state, key_store, headers))
.await
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("Unable to encrypt outgoing webhook custom HTTP headers")?;
let payout_link_config = self
.payout_link_config
.map(|payout_conf| match payout_conf.config.validate() {
Ok(_) => Ok(payout_conf.foreign_into()),
Err(e) => Err(error_stack::report!(
errors::ApiErrorResponse::InvalidRequestData {
message: e.to_string()
}
)),
})
.transpose()?;
Ok(domain::BusinessProfile {
profile_id,
merchant_id: merchant_id.clone(),
profile_name,
created_at: current_time,
modified_at: current_time,
return_url: self.return_url.map(|return_url| return_url.to_string()),
enable_payment_response_hash: self.enable_payment_response_hash.unwrap_or(true),
payment_response_hash_key: Some(payment_response_hash_key),
redirect_to_merchant_with_http_post: self
.redirect_to_merchant_with_http_post
.unwrap_or(true),
webhook_details,
metadata: self.metadata,
is_recon_enabled: false,
applepay_verified_domains: self.applepay_verified_domains,
payment_link_config,
session_expiry: self
.session_expiry
.map(i64::from)
.or(Some(common_utils::consts::DEFAULT_SESSION_EXPIRY)),
authentication_connector_details: self
.authentication_connector_details
.map(ForeignInto::foreign_into),
payout_link_config,
is_connector_agnostic_mit_enabled: self.is_connector_agnostic_mit_enabled,
is_extended_card_info_enabled: None,
extended_card_info_config: None,
use_billing_as_payment_method_billing: self
.use_billing_as_payment_method_billing
.or(Some(true)),
collect_shipping_details_from_wallet_connector: self
.collect_shipping_details_from_wallet_connector
.or(Some(false)),
collect_billing_details_from_wallet_connector: self
.collect_billing_details_from_wallet_connector
.or(Some(false)),
outgoing_webhook_custom_http_headers: outgoing_webhook_custom_http_headers
.map(Into::into),
routing_algorithm_id: None,
frm_routing_algorithm_id: None,
payout_routing_algorithm_id: None,
order_fulfillment_time: self
.order_fulfillment_time
.map(|order_fulfillment_time| i64::from(order_fulfillment_time.into_inner()))
.or(Some(common_utils::consts::DEFAULT_ORDER_FULFILLMENT_TIME)),
order_fulfillment_time_origin: self.order_fulfillment_time_origin,
default_fallback_routing: None,
})
}
}
#[cfg(feature = "olap")]
pub async fn create_business_profile(
state: SessionState,
request: api::BusinessProfileCreate,
merchant_id: &id_type::MerchantId,
) -> RouterResponse<api_models::admin::BusinessProfileResponse> {
if let Some(session_expiry) = &request.session_expiry {
helpers::validate_session_expiry(session_expiry.to_owned())?;
}
if let Some(intent_fulfillment_expiry) = &request.intent_fulfillment_time {
helpers::validate_intent_fulfillment_expiry(intent_fulfillment_expiry.to_owned())?;
}
let db = state.store.as_ref();
let key_manager_state = &(&state).into();
let key_store = db
@ -3339,20 +3575,33 @@ pub async fn create_business_profile(
.await
.to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
if let Some(ref routing_algorithm) = request.routing_algorithm {
let _: api_models::routing::RoutingAlgorithm = routing_algorithm
.clone()
.parse_value("RoutingAlgorithm")
.change_context(errors::ApiErrorResponse::InvalidDataValue {
field_name: "routing_algorithm",
})
.attach_printable("Invalid routing algorithm given")?;
}
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "business_profile_v2")
))]
let business_profile = request
.create_domain_model_from_request(&state, &merchant_account, &key_store)
.await?;
let business_profile =
create_and_insert_business_profile(&state, request, merchant_account.clone(), &key_store)
.await?;
#[cfg(all(feature = "v2", feature = "business_profile_v2"))]
let business_profile = request
.create_domain_model_from_request(&state, &key_store, merchant_account.get_id())
.await?;
let profile_id = business_profile.profile_id.clone();
let business_profile = db
.insert_business_profile(key_manager_state, &key_store, business_profile)
.await
.to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError {
message: format!("Business Profile with the profile_id {profile_id} already exists"),
})
.attach_printable("Failed to insert Business profile because of duplication error")?;
#[cfg(all(
any(feature = "v1", feature = "v2"),
not(feature = "business_profile_v2")
))]
if merchant_account.default_profile.is_some() {
let unset_default_profile = domain::MerchantAccountUpdate::UnsetDefaultProfile;
db.update_merchant(
@ -3372,19 +3621,6 @@ pub async fn create_business_profile(
))
}
#[cfg(all(
feature = "v2",
feature = "merchant_account_v2",
feature = "business_profile_v2"
))]
pub async fn create_business_profile(
_state: SessionState,
_request: api::BusinessProfileCreate,
_merchant_id: &id_type::MerchantId,
) -> RouterResponse<api_models::admin::BusinessProfileResponse> {
todo!()
}
pub async fn list_business_profile(
state: SessionState,
merchant_id: id_type::MerchantId,

View File

@ -4034,7 +4034,7 @@ pub async fn list_customer_payment_method(
let intent_fulfillment_time = business_profile
.as_ref()
.and_then(|b_profile| b_profile.intent_fulfillment_time)
.and_then(|b_profile| b_profile.get_order_fulfillment_time())
.unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME);
let hyperswitch_token_data = pm_list_context
@ -4307,7 +4307,7 @@ impl SavedPMLPaymentsInfo {
let intent_fulfillment_time = self
.business_profile
.as_ref()
.and_then(|b_profile| b_profile.intent_fulfillment_time)
.and_then(|b_profile| b_profile.get_order_fulfillment_time())
.unwrap_or(consts::DEFAULT_INTENT_FULFILLMENT_TIME);
ParentPaymentMethodToken::create_key_for_token((token, pma.payment_method))
@ -4513,7 +4513,9 @@ async fn generate_saved_pm_response(
pi.is_connector_agnostic_mit_enabled,
pi.requires_cvv,
pi.off_session_payment_flag,
pi.business_profile.map(|profile| profile.profile_id),
pi.business_profile
.as_ref()
.map(|profile| profile.profile_id.clone()),
)
})
.unwrap_or((false, false, false, Default::default()));

View File

@ -4016,7 +4016,7 @@ where
routing_data,
connector_data,
mandate_type,
business_profile.is_connector_agnostic_mit_enabled.clone(),
business_profile.is_connector_agnostic_mit_enabled,
)
.await
}

View File

@ -2146,7 +2146,7 @@ pub async fn store_in_vault_and_generate_ppmt(
});
let intent_fulfillment_time = business_profile
.and_then(|b_profile| b_profile.intent_fulfillment_time)
.and_then(|b_profile| b_profile.get_order_fulfillment_time())
.unwrap_or(consts::DEFAULT_FULFILLMENT_TIME);
if let Some(key_for_hyperswitch_token) = key_for_hyperswitch_token {

View File

@ -312,7 +312,7 @@ impl SurchargeMetadata {
));
}
let intent_fulfillment_time = business_profile
.intent_fulfillment_time
.get_order_fulfillment_time()
.unwrap_or(router_consts::DEFAULT_FULFILLMENT_TIME);
redis_conn
.set_hash_fields(&redis_key, value_list, Some(intent_fulfillment_time))

View File

@ -306,7 +306,7 @@ impl MerchantConnectorAccounts {
where
T: std::hash::Hash + Eq,
{
self.filter_and_map(|mca| mca.profile_id.as_deref() == Some(profile_id), func)
self.filter_and_map(|mca| mca.profile_id == profile_id, func)
}
}