Skip to content
Merged
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
17 changes: 4 additions & 13 deletions crates/api_models/src/open_router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@ pub use euclid::{
};
use serde::{Deserialize, Serialize};

use crate::enums::{Currency, PaymentMethod, RoutableConnectors};
use crate::enums::{Currency, PaymentMethod};

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct OpenRouterDecideGatewayRequest {
pub payment_info: PaymentInfo,
pub merchant_id: id_type::ProfileId,
pub eligible_gateway_list: Option<Vec<RoutableConnectors>>,
pub eligible_gateway_list: Option<Vec<String>>,
pub ranking_algorithm: Option<RankingAlgorithm>,
pub elimination_enabled: Option<bool>,
}
Expand Down Expand Up @@ -80,7 +80,7 @@ pub struct UnifiedError {
#[serde(rename_all = "camelCase")]
pub struct UpdateScorePayload {
pub merchant_id: id_type::ProfileId,
pub gateway: RoutableConnectors,
pub gateway: String,
pub status: TxnStatus,
pub payment_id: id_type::PaymentId,
}
Expand All @@ -91,7 +91,7 @@ pub enum TxnStatus {
Started,
AuthenticationFailed,
JuspayDeclined,
PendingVBV,
PendingVbv,
VBVSuccessful,
Authorized,
AuthorizationFailed,
Expand All @@ -111,12 +111,3 @@ pub enum TxnStatus {
Failure,
Declined,
}

impl From<bool> for TxnStatus {
fn from(value: bool) -> Self {
match value {
true => Self::Charged,
_ => Self::Failure,
}
}
}
11 changes: 11 additions & 0 deletions crates/api_models/src/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,17 @@ impl DynamicRoutingAlgorithmRef {
pub fn update_volume_split(&mut self, volume: Option<u8>) {
self.dynamic_routing_volume_split = volume
}

pub fn is_elimination_enabled(&self) -> bool {
self.elimination_routing_algorithm
.as_ref()
.map(|elimination_routing| {
elimination_routing.enabled_feature
== DynamicRoutingFeatures::DynamicConnectorSelection
|| elimination_routing.enabled_feature == DynamicRoutingFeatures::Metrics
})
.unwrap_or_default()
}
}

#[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)]
Expand Down
49 changes: 34 additions & 15 deletions crates/router/src/core/payments/routing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1516,6 +1516,7 @@ pub async fn perform_open_routing(
profile.get_id().get_string_repr()
);

let is_elimination_enabled = dynamic_routing_algo_ref.is_elimination_enabled();
let connectors = dynamic_routing_algo_ref
.success_based_algorithm
.async_map(|algo| {
Expand All @@ -1524,16 +1525,34 @@ pub async fn perform_open_routing(
routable_connectors.clone(),
profile.get_id(),
algo,
payment_data,
&payment_data,
is_elimination_enabled,
)
})
.await
.transpose()
.inspect_err(|e| logger::error!(dynamic_routing_error=?e))
.ok()
.flatten()
.transpose()?
.unwrap_or(routable_connectors);

if is_elimination_enabled {
// This will initiate the elimination process for the connector.
// Penalize the elimination score of the connector before making a payment.
// Once the payment is made, we will update the score based on the payment status
if let Some(connector) = connectors.first() {
logger::debug!(
"penalizing the elimination score of the gateway with id {} in open router for profile {}",
connector, profile.get_id().get_string_repr()
);
update_success_rate_score_with_open_router(
state,
connector.clone(),
profile.get_id(),
&payment_data.payment_id,
common_enums::AttemptStatus::AuthenticationPending,
)
.await?
}
}

Ok(connectors)
}

Expand Down Expand Up @@ -1634,7 +1653,8 @@ pub async fn perform_success_based_routing_with_open_router(
mut routable_connectors: Vec<api_routing::RoutableConnectorChoice>,
profile_id: &common_utils::id_type::ProfileId,
success_based_algo_ref: api_routing::SuccessBasedAlgorithm,
payment_attempt: oss_storage::PaymentAttempt,
payment_attempt: &oss_storage::PaymentAttempt,
is_elimination_enabled: bool,
) -> RoutingResult<Vec<api_routing::RoutableConnectorChoice>> {
if success_based_algo_ref.enabled_feature
== api_routing::DynamicRoutingFeatures::DynamicConnectorSelection
Expand All @@ -1646,11 +1666,9 @@ pub async fn perform_success_based_routing_with_open_router(

let open_router_req_body = OpenRouterDecideGatewayRequest::construct_sr_request(
payment_attempt,
routable_connectors
.iter()
.map(|gateway| gateway.connector)
.collect::<Vec<_>>(),
routable_connectors.clone(),
Some(or_types::RankingAlgorithm::SrBasedRouting),
is_elimination_enabled,
);

let url = format!("{}/{}", &state.conf.open_router.url, "decide-gateway");
Expand Down Expand Up @@ -1724,15 +1742,15 @@ pub async fn perform_success_based_routing_with_open_router(
#[instrument(skip_all)]
pub async fn update_success_rate_score_with_open_router(
state: &SessionState,
payment_connector: common_enums::RoutableConnectors,
payment_connector: api_routing::RoutableConnectorChoice,
profile_id: &common_utils::id_type::ProfileId,
payment_id: &common_utils::id_type::PaymentId,
payment_status: bool,
payment_status: common_enums::AttemptStatus,
) -> RoutingResult<()> {
let open_router_req_body = or_types::UpdateScorePayload {
merchant_id: profile_id.clone(),
gateway: payment_connector,
status: payment_status.into(),
gateway: payment_connector.to_string(),
status: payment_status.foreign_into(),
payment_id: payment_id.clone(),
};

Expand Down Expand Up @@ -1763,7 +1781,8 @@ pub async fn update_success_rate_score_with_open_router(
)?;

logger::debug!(
"Open router update_gateway_score response: {:?}",
"Open router update_gateway_score response for gateway with id {}: {:?}",
payment_connector,
update_score_resp
);

Expand Down
70 changes: 53 additions & 17 deletions crates/router/src/core/routing/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use std::str::FromStr;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use std::sync::Arc;

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use api_models::open_router;
use api_models::routing as routing_types;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use common_utils::ext_traits::ValueExt;
Expand Down Expand Up @@ -39,6 +41,8 @@ use storage_impl::redis::cache::Cacheable;
use crate::db::errors::StorageErrorExt;
#[cfg(feature = "v2")]
use crate::types::domain::MerchantConnectorAccount;
#[cfg(all(feature = "dynamic_routing", feature = "v1"))]
use crate::types::transformers::ForeignFrom;
use crate::{
core::errors::{self, RouterResult},
db::StorageInterface,
Expand Down Expand Up @@ -729,20 +733,27 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
},
)?;

let payment_status_attribute =
get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status);
let routable_connector = routing_types::RoutableConnectorChoice {
choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
connector: common_enums::RoutableConnectors::from_str(payment_connector.as_str())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to infer routable_connector from connector")?,
merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
};

let should_route_to_open_router = state.conf.open_router.enabled;

if should_route_to_open_router {
logger::debug!(
"performing update-gateway-score for gateway with id {} in open router for profile: {}",
routable_connector, profile_id.get_string_repr()
);
routing::payments_routing::update_success_rate_score_with_open_router(
state,
common_enums::RoutableConnectors::from_str(payment_connector.as_str())
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable("unable to infer routable_connector from connector")?,
routable_connector.clone(),
profile_id,
&payment_attempt.payment_id,
payment_status_attribute == common_enums::AttemptStatus::Charged,
payment_attempt.status,
)
.await
.change_context(errors::ApiErrorResponse::InternalServerError)
Expand All @@ -751,6 +762,9 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
return Ok(());
}

let payment_status_attribute =
get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status);

let success_based_routing_configs = fetch_dynamic_routing_configs::<
routing_types::SuccessBasedRoutingConfig,
>(
Expand Down Expand Up @@ -972,17 +986,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing(
success_based_routing_configs,
success_based_routing_config_params,
vec![routing_types::RoutableConnectorChoiceWithStatus::new(
routing_types::RoutableConnectorChoice {
choice_kind: api_models::routing::RoutableChoiceKind::FullStruct,
connector: common_enums::RoutableConnectors::from_str(
payment_connector.as_str(),
)
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to infer routable_connector from connector",
)?,
merchant_connector_id: payment_attempt.merchant_connector_id.clone(),
},
routable_connector,
payment_status_attribute == common_enums::AttemptStatus::Charged,
)],
state.get_grpc_headers(),
Expand Down Expand Up @@ -1350,6 +1354,38 @@ fn get_desired_payment_status_for_dynamic_routing_metrics(
}
}

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
impl ForeignFrom<common_enums::AttemptStatus> for open_router::TxnStatus {
fn foreign_from(attempt_status: common_enums::AttemptStatus) -> Self {
match attempt_status {
common_enums::AttemptStatus::Started => Self::Started,
common_enums::AttemptStatus::AuthenticationFailed => Self::AuthenticationFailed,
common_enums::AttemptStatus::RouterDeclined => Self::JuspayDeclined,
common_enums::AttemptStatus::AuthenticationPending => Self::PendingVbv,
common_enums::AttemptStatus::AuthenticationSuccessful => Self::VBVSuccessful,
common_enums::AttemptStatus::Authorized => Self::Authorized,
common_enums::AttemptStatus::AuthorizationFailed => Self::AuthorizationFailed,
common_enums::AttemptStatus::Charged => Self::Charged,
common_enums::AttemptStatus::Authorizing => Self::Authorizing,
common_enums::AttemptStatus::CodInitiated => Self::CODInitiated,
common_enums::AttemptStatus::Voided => Self::Voided,
common_enums::AttemptStatus::VoidInitiated => Self::VoidInitiated,
common_enums::AttemptStatus::CaptureInitiated => Self::CaptureInitiated,
common_enums::AttemptStatus::CaptureFailed => Self::CaptureFailed,
common_enums::AttemptStatus::VoidFailed => Self::VoidFailed,
common_enums::AttemptStatus::AutoRefunded => Self::AutoRefunded,
common_enums::AttemptStatus::PartialCharged => Self::PartialCharged,
common_enums::AttemptStatus::PartialChargedAndChargeable => Self::ToBeCharged,
common_enums::AttemptStatus::Unresolved => Self::Pending,
common_enums::AttemptStatus::Pending => Self::Pending,
common_enums::AttemptStatus::Failure => Self::Failure,
common_enums::AttemptStatus::PaymentMethodAwaited => Self::Pending,
common_enums::AttemptStatus::ConfirmationAwaited => Self::Pending,
common_enums::AttemptStatus::DeviceDataCollectionPending => Self::Pending,
}
}
}

#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
fn get_dynamic_routing_based_metrics_outcome_for_payment(
payment_status_attribute: common_enums::AttemptStatus,
Expand Down
30 changes: 19 additions & 11 deletions crates/router/src/core/routing/transformers.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use api_models::open_router::{OpenRouterDecideGatewayRequest, PaymentInfo, RankingAlgorithm};
use api_models::routing::{
MerchantRoutingAlgorithm, RoutingAlgorithm as Algorithm, RoutingAlgorithmKind,
RoutingDictionaryRecord,
};
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
use common_enums::RoutableConnectors;
use api_models::{
open_router::{OpenRouterDecideGatewayRequest, PaymentInfo, RankingAlgorithm},
routing::RoutableConnectorChoice,
};
use common_utils::ext_traits::ValueExt;
use diesel_models::{
enums as storage_enums,
Expand Down Expand Up @@ -108,9 +109,10 @@ impl From<&routing::TransactionData<'_>> for storage_enums::TransactionType {
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
pub trait OpenRouterDecideGatewayRequestExt {
fn construct_sr_request(
attempt: PaymentAttempt,
eligible_gateway_list: Vec<RoutableConnectors>,
attempt: &PaymentAttempt,
eligible_gateway_list: Vec<RoutableConnectorChoice>,
ranking_algorithm: Option<RankingAlgorithm>,
is_elimination_enabled: bool,
) -> Self
where
Self: Sized;
Expand All @@ -119,24 +121,30 @@ pub trait OpenRouterDecideGatewayRequestExt {
#[cfg(all(feature = "v1", feature = "dynamic_routing"))]
impl OpenRouterDecideGatewayRequestExt for OpenRouterDecideGatewayRequest {
fn construct_sr_request(
attempt: PaymentAttempt,
eligible_gateway_list: Vec<RoutableConnectors>,
attempt: &PaymentAttempt,
eligible_gateway_list: Vec<RoutableConnectorChoice>,
ranking_algorithm: Option<RankingAlgorithm>,
is_elimination_enabled: bool,
) -> Self {
Self {
payment_info: PaymentInfo {
payment_id: attempt.payment_id,
payment_id: attempt.payment_id.clone(),
amount: attempt.net_amount.get_order_amount(),
currency: attempt.currency.unwrap_or(storage_enums::Currency::USD),
payment_type: "ORDER_PAYMENT".to_string(),
// payment_method_type: attempt.payment_method_type.clone().unwrap(),
payment_method_type: "UPI".into(), // TODO: once open-router makes this field string, we can send from attempt
payment_method: attempt.payment_method.unwrap_or_default(),
},
merchant_id: attempt.profile_id,
eligible_gateway_list: Some(eligible_gateway_list),
merchant_id: attempt.profile_id.clone(),
eligible_gateway_list: Some(
eligible_gateway_list
.into_iter()
.map(|connector| connector.to_string())
.collect(),
),
ranking_algorithm,
elimination_enabled: None,
elimination_enabled: Some(is_elimination_enabled),
}
}
}
Loading