From 4dea30ffa0d9bf636af3757c998e12a23f4769bd Mon Sep 17 00:00:00 2001 From: Gaurav Rawat <104276743+GauravRawat369@users.noreply.github.com> Date: Tue, 29 Jul 2025 16:18:51 +0530 Subject: [PATCH] feat(routing): Add api-refs for new decision engine endpoints (#8709) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/docs.json | 5 +- api-reference/v1/openapi_spec_v1.json | 518 +++++++++++++++++- .../v1/routing/routing--evaluate.mdx | 3 + .../v1/routing/routing--feedback.mdx | 3 + .../v1/routing/routing--rule-evaluate.mdx | 3 + crates/api_models/src/open_router.rs | 22 +- crates/api_models/src/routing.rs | 119 ++++ crates/openapi/src/openapi.rs | 19 + crates/openapi/src/routes/routing.rs | 66 +++ .../router/src/core/payments/routing/utils.rs | 108 +--- crates/router/src/routes/routing.rs | 10 +- 11 files changed, 755 insertions(+), 121 deletions(-) create mode 100644 api-reference/v1/routing/routing--evaluate.mdx create mode 100644 api-reference/v1/routing/routing--feedback.mdx create mode 100644 api-reference/v1/routing/routing--rule-evaluate.mdx diff --git a/api-reference/docs.json b/api-reference/docs.json index f46b0f2e07..17050ceb9f 100644 --- a/api-reference/docs.json +++ b/api-reference/docs.json @@ -220,7 +220,10 @@ "v1/routing/routing--retrieve-default-for-profile", "v1/routing/routing--update-default-for-profile", "v1/routing/routing--retrieve", - "v1/routing/routing--activate-config" + "v1/routing/routing--activate-config", + "v1/routing/routing--evaluate", + "v1/routing/routing--feedback", + "v1/routing/routing--rule-evaluate" ] }, { diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index cdfa214a3c..fc8d782a82 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -5174,6 +5174,162 @@ ] } }, + "/routing/evaluate": { + "post": { + "tags": [ + "Routing" + ], + "summary": "Routing - Evaluate", + "description": "Evaluate routing rules", + "operationId": "Evaluate routing rules", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OpenRouterDecideGatewayRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Routing rules evaluated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/DecideGatewayResponse" + } + } + } + }, + "400": { + "description": "Request body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/routing/feedback": { + "post": { + "tags": [ + "Routing" + ], + "summary": "Routing - Feedback", + "description": "Update gateway scores for dynamic routing", + "operationId": "Update gateway scores", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScorePayload" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Gateway score updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/UpdateScoreResponse" + } + } + } + }, + "400": { + "description": "Request body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/routing/rule/evaluate": { + "post": { + "tags": [ + "Routing" + ], + "summary": "Routing - Rule Evaluate", + "description": "Evaluate routing rules", + "operationId": "Evaluate routing rules (alternative)", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingEvaluateRequest" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Routing rules evaluated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingEvaluateResponse" + } + } + } + }, + "400": { + "description": "Request body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/blocklist": { "delete": { "tags": [ @@ -13367,6 +13523,80 @@ } } }, + "DeRoutableConnectorChoice": { + "type": "object", + "description": "Routable Connector chosen for a payment", + "required": [ + "gateway_name", + "gateway_id" + ], + "properties": { + "gateway_name": { + "$ref": "#/components/schemas/RoutableConnectors" + }, + "gateway_id": { + "type": "string" + } + } + }, + "DecideGatewayResponse": { + "type": "object", + "properties": { + "decided_gateway": { + "type": "string", + "nullable": true + }, + "gateway_priority_map": { + "nullable": true + }, + "filter_wise_gateways": { + "nullable": true + }, + "priority_logic_tag": { + "type": "string", + "nullable": true + }, + "routing_approach": { + "type": "string", + "nullable": true + }, + "gateway_before_evaluation": { + "type": "string", + "nullable": true + }, + "priority_logic_output": { + "allOf": [ + { + "$ref": "#/components/schemas/PriorityLogicOutput" + } + ], + "nullable": true + }, + "reset_approach": { + "type": "string", + "nullable": true + }, + "routing_dimension": { + "type": "string", + "nullable": true + }, + "routing_dimension_level": { + "type": "string", + "nullable": true + }, + "is_scheduled_outage": { + "type": "boolean", + "nullable": true + }, + "is_dynamic_mga_enabled": { + "type": "boolean", + "nullable": true + }, + "gateway_mga_id_map": { + "nullable": true + } + } + }, "DecisionEngineEliminationData": { "type": "object", "required": [ @@ -18685,6 +18915,40 @@ } } }, + "OpenRouterDecideGatewayRequest": { + "type": "object", + "required": [ + "paymentInfo", + "merchantId" + ], + "properties": { + "paymentInfo": { + "$ref": "#/components/schemas/PaymentInfo" + }, + "merchantId": { + "type": "string" + }, + "eligibleGatewayList": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "rankingAlgorithm": { + "allOf": [ + { + "$ref": "#/components/schemas/RankingAlgorithm" + } + ], + "nullable": true + }, + "eliminationEnabled": { + "type": "boolean", + "nullable": true + } + } + }, "OrderDetailsWithAmount": { "type": "object", "required": [ @@ -19527,6 +19791,49 @@ } } }, + "PaymentId": { + "type": "string", + "description": "A type for payment_id that can be used for payment ids" + }, + "PaymentInfo": { + "type": "object", + "required": [ + "paymentId", + "amount", + "currency", + "paymentType", + "paymentMethodType", + "paymentMethod" + ], + "properties": { + "paymentId": { + "type": "string" + }, + "amount": { + "$ref": "#/components/schemas/MinorUnit" + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "paymentType": { + "type": "string" + }, + "metadata": { + "type": "string", + "nullable": true + }, + "paymentMethodType": { + "type": "string" + }, + "paymentMethod": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "cardIsin": { + "type": "string", + "nullable": true + } + } + }, "PaymentLinkBackgroundImageConfig": { "type": "object", "required": [ @@ -26544,6 +26851,66 @@ }, "additionalProperties": false }, + "PriorityLogicData": { + "type": "object", + "properties": { + "name": { + "type": "string", + "nullable": true + }, + "status": { + "type": "string", + "nullable": true + }, + "failure_reason": { + "type": "string", + "nullable": true + } + } + }, + "PriorityLogicOutput": { + "type": "object", + "properties": { + "isEnforcement": { + "type": "boolean", + "nullable": true + }, + "gws": { + "type": "array", + "items": { + "type": "string" + }, + "nullable": true + }, + "priorityLogicTag": { + "type": "string", + "nullable": true + }, + "gatewayReferenceIds": { + "type": "object", + "additionalProperties": { + "type": "string" + }, + "nullable": true + }, + "primaryLogic": { + "allOf": [ + { + "$ref": "#/components/schemas/PriorityLogicData" + } + ], + "nullable": true + }, + "fallbackLogic": { + "allOf": [ + { + "$ref": "#/components/schemas/PriorityLogicData" + } + ], + "nullable": true + } + } + }, "ProcessorPaymentToken": { "type": "object", "description": "Processor payment token for MIT payments where payment_method_data is not available", @@ -26989,6 +27356,10 @@ } } }, + "ProfileId": { + "type": "string", + "description": "A type for profile_id that can be used for business profile ids" + }, "ProfileResponse": { "type": "object", "required": [ @@ -27325,6 +27696,14 @@ } } }, + "RankingAlgorithm": { + "type": "string", + "enum": [ + "SR_BASED_ROUTING", + "PL_BASED_ROUTING", + "NTW_BASED_ROUTING" + ] + }, "RealTimePaymentData": { "oneOf": [ { @@ -28666,6 +29045,56 @@ } } }, + "RoutingEvaluateRequest": { + "type": "object", + "required": [ + "created_by", + "parameters", + "fallback_output" + ], + "properties": { + "created_by": { + "type": "string" + }, + "parameters": { + "type": "object", + "description": "Parameters that can be used in the routing evaluate request.\neg: {\"parameters\": {\n\"payment_method\": {\"type\": \"enum_variant\", \"value\": \"card\"},\n\"payment_method_type\": {\"type\": \"enum_variant\", \"value\": \"credit\"},\n\"amount\": {\"type\": \"number\", \"value\": 10},\n\"currency\": {\"type\": \"str_value\", \"value\": \"INR\"},\n\"authentication_type\": {\"type\": \"enum_variant\", \"value\": \"three_ds\"},\n\"card_bin\": {\"type\": \"str_value\", \"value\": \"424242\"},\n\"capture_method\": {\"type\": \"enum_variant\", \"value\": \"scheduled\"},\n\"business_country\": {\"type\": \"str_value\", \"value\": \"IN\"},\n\"billing_country\": {\"type\": \"str_value\", \"value\": \"IN\"},\n\"business_label\": {\"type\": \"str_value\", \"value\": \"business_label\"},\n\"setup_future_usage\": {\"type\": \"enum_variant\", \"value\": \"off_session\"},\n\"card_network\": {\"type\": \"enum_variant\", \"value\": \"visa\"},\n\"payment_type\": {\"type\": \"enum_variant\", \"value\": \"recurring_mandate\"},\n\"mandate_type\": {\"type\": \"enum_variant\", \"value\": \"single_use\"},\n\"mandate_acceptance_type\": {\"type\": \"enum_variant\", \"value\": \"online\"},\n\"metadata\":{\"type\": \"metadata_variant\", \"value\": {\"key\": \"key1\", \"value\": \"value1\"}}\n}}" + }, + "fallback_output": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DeRoutableConnectorChoice" + } + } + } + }, + "RoutingEvaluateResponse": { + "type": "object", + "required": [ + "status", + "output", + "evaluated_output", + "eligible_connectors" + ], + "properties": { + "status": { + "type": "string" + }, + "output": {}, + "evaluated_output": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutableConnectorChoice" + } + }, + "eligible_connectors": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RoutableConnectorChoice" + } + } + } + }, "RoutingKind": { "oneOf": [ { @@ -30529,6 +30958,33 @@ "external" ] }, + "TxnStatus": { + "type": "string", + "enum": [ + "STARTED", + "AUTHENTICATION_FAILED", + "JUSPAY_DECLINED", + "PENDING_VBV", + "V_B_V_SUCCESSFUL", + "AUTHORIZED", + "AUTHORIZATION_FAILED", + "CHARGED", + "AUTHORIZING", + "C_O_D_INITIATED", + "VOIDED", + "VOID_INITIATED", + "NOP", + "CAPTURE_INITIATED", + "CAPTURE_FAILED", + "VOID_FAILED", + "AUTO_REFUNDED", + "PARTIAL_CHARGED", + "TO_BE_CHARGED", + "PENDING", + "FAILURE", + "DECLINED" + ] + }, "UIWidgetFormLayout": { "type": "string", "enum": [ @@ -30565,6 +31021,40 @@ }, "additionalProperties": false }, + "UpdateScorePayload": { + "type": "object", + "required": [ + "merchantId", + "gateway", + "status", + "paymentId" + ], + "properties": { + "merchantId": { + "type": "string" + }, + "gateway": { + "type": "string" + }, + "status": { + "$ref": "#/components/schemas/TxnStatus" + }, + "paymentId": { + "type": "string" + } + } + }, + "UpdateScoreResponse": { + "type": "object", + "required": [ + "message" + ], + "properties": { + "message": { + "type": "string" + } + } + }, "UpiAdditionalData": { "oneOf": [ { @@ -30672,7 +31162,10 @@ ] }, "value": { - "$ref": "#/components/schemas/MinorUnit" + "type": "integer", + "format": "int64", + "description": "Represents a number literal", + "minimum": 0 } } }, @@ -30732,6 +31225,25 @@ } } }, + { + "type": "object", + "required": [ + "type", + "value" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "global_ref" + ] + }, + "value": { + "type": "string", + "description": "Represents a global reference, which is a reference to a global variable" + } + } + }, { "type": "object", "required": [ @@ -30748,7 +31260,9 @@ "value": { "type": "array", "items": { - "$ref": "#/components/schemas/MinorUnit" + "type": "integer", + "format": "int64", + "minimum": 0 }, "description": "Represents an array of numbers. This is basically used for\n\"one of the given numbers\" operations\neg: payment.method.amount = (1, 2, 3)" } diff --git a/api-reference/v1/routing/routing--evaluate.mdx b/api-reference/v1/routing/routing--evaluate.mdx new file mode 100644 index 0000000000..d4fe104628 --- /dev/null +++ b/api-reference/v1/routing/routing--evaluate.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /routing/evaluate +--- diff --git a/api-reference/v1/routing/routing--feedback.mdx b/api-reference/v1/routing/routing--feedback.mdx new file mode 100644 index 0000000000..60fb7cde6d --- /dev/null +++ b/api-reference/v1/routing/routing--feedback.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /routing/feedback +--- diff --git a/api-reference/v1/routing/routing--rule-evaluate.mdx b/api-reference/v1/routing/routing--rule-evaluate.mdx new file mode 100644 index 0000000000..8715b56d5c --- /dev/null +++ b/api-reference/v1/routing/routing--rule-evaluate.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /routing/rule/evaluate +--- diff --git a/crates/api_models/src/open_router.rs b/crates/api_models/src/open_router.rs index aefbd3eb21..02ded9713f 100644 --- a/crates/api_models/src/open_router.rs +++ b/crates/api_models/src/open_router.rs @@ -16,17 +16,18 @@ use crate::{ payment_methods, }; -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct OpenRouterDecideGatewayRequest { pub payment_info: PaymentInfo, + #[schema(value_type = String)] pub merchant_id: id_type::ProfileId, pub eligible_gateway_list: Option>, pub ranking_algorithm: Option, pub elimination_enabled: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct DecideGatewayResponse { pub decided_gateway: Option, pub gateway_priority_map: Option, @@ -43,7 +44,7 @@ pub struct DecideGatewayResponse { pub gateway_mga_id_map: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct PriorityLogicOutput { pub is_enforcement: Option, @@ -54,14 +55,14 @@ pub struct PriorityLogicOutput { pub fallback_logic: Option, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] pub struct PriorityLogicData { pub name: Option, pub status: Option, pub failure_reason: Option, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RankingAlgorithm { SrBasedRouting, @@ -69,9 +70,10 @@ pub enum RankingAlgorithm { NtwBasedRouting, } -#[derive(Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "camelCase")] pub struct PaymentInfo { + #[schema(value_type = String)] pub payment_id: id_type::PaymentId, pub amount: MinorUnit, pub currency: Currency, @@ -189,21 +191,23 @@ pub struct UnifiedError { pub developer_message: String, } -#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)] #[serde(rename_all = "camelCase")] pub struct UpdateScorePayload { + #[schema(value_type = String)] pub merchant_id: id_type::ProfileId, pub gateway: String, pub status: TxnStatus, + #[schema(value_type = String)] pub payment_id: id_type::PaymentId, } -#[derive(Debug, Serialize, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone, ToSchema)] pub struct UpdateScoreResponse { pub message: String, } -#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TxnStatus { Started, diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index a900d84150..ef5a9422e2 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -1444,3 +1444,122 @@ pub enum RoutingResultSource { /// Inbuilt Hyperswitch Routing Engine HyperswitchRouting, } +//TODO: temporary change will be refactored afterwards +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, ToSchema)] +pub struct RoutingEvaluateRequest { + pub created_by: String, + #[schema(value_type = Object)] + ///Parameters that can be used in the routing evaluate request. + ///eg: {"parameters": { + /// "payment_method": {"type": "enum_variant", "value": "card"}, + /// "payment_method_type": {"type": "enum_variant", "value": "credit"}, + /// "amount": {"type": "number", "value": 10}, + /// "currency": {"type": "str_value", "value": "INR"}, + /// "authentication_type": {"type": "enum_variant", "value": "three_ds"}, + /// "card_bin": {"type": "str_value", "value": "424242"}, + /// "capture_method": {"type": "enum_variant", "value": "scheduled"}, + /// "business_country": {"type": "str_value", "value": "IN"}, + /// "billing_country": {"type": "str_value", "value": "IN"}, + /// "business_label": {"type": "str_value", "value": "business_label"}, + /// "setup_future_usage": {"type": "enum_variant", "value": "off_session"}, + /// "card_network": {"type": "enum_variant", "value": "visa"}, + /// "payment_type": {"type": "enum_variant", "value": "recurring_mandate"}, + /// "mandate_type": {"type": "enum_variant", "value": "single_use"}, + /// "mandate_acceptance_type": {"type": "enum_variant", "value": "online"}, + /// "metadata":{"type": "metadata_variant", "value": {"key": "key1", "value": "value1"}} + /// }} + pub parameters: std::collections::HashMap>, + pub fallback_output: Vec, +} +impl common_utils::events::ApiEventMetric for RoutingEvaluateRequest {} + +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] +pub struct RoutingEvaluateResponse { + pub status: String, + pub output: serde_json::Value, + #[serde(deserialize_with = "deserialize_connector_choices")] + pub evaluated_output: Vec, + #[serde(deserialize_with = "deserialize_connector_choices")] + pub eligible_connectors: Vec, +} +impl common_utils::events::ApiEventMetric for RoutingEvaluateResponse {} + +fn deserialize_connector_choices<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let infos = Vec::::deserialize(deserializer)?; + Ok(infos + .into_iter() + .map(RoutableConnectorChoice::from) + .collect()) +} + +impl From for RoutableConnectorChoice { + fn from(choice: DeRoutableConnectorChoice) -> Self { + Self { + choice_kind: RoutableChoiceKind::FullStruct, + connector: choice.gateway_name, + merchant_connector_id: choice.gateway_id, + } + } +} + +/// Routable Connector chosen for a payment +#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +pub struct DeRoutableConnectorChoice { + pub gateway_name: RoutableConnectors, + #[schema(value_type = String)] + pub gateway_id: Option, +} +/// Represents a value in the DSL +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)] +#[serde(tag = "type", content = "value", rename_all = "snake_case")] +pub enum ValueType { + /// Represents a number literal + Number(u64), + /// Represents an enum variant + EnumVariant(String), + /// Represents a Metadata variant + MetadataVariant(MetadataValue), + /// Represents a arbitrary String value + StrValue(String), + /// Represents a global reference, which is a reference to a global variable + GlobalRef(String), + /// Represents an array of numbers. This is basically used for + /// "one of the given numbers" operations + /// eg: payment.method.amount = (1, 2, 3) + NumberArray(Vec), + /// Similar to NumberArray but for enum variants + /// eg: payment.method.cardtype = (debit, credit) + EnumVariantArray(Vec), + /// Like a number array but can include comparisons. Useful for + /// conditions like "500 < amount < 1000" + /// eg: payment.amount = (> 500, < 1000) + NumberComparisonArray(Vec), +} +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)] +pub struct MetadataValue { + pub key: String, + pub value: String, +} +/// Represents a number comparison for "NumberComparisonArrayValue" +#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct NumberComparison { + pub comparison_type: ComparisonType, + pub number: u64, +} +/// Conditional comparison type +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ComparisonType { + Equal, + NotEqual, + LessThan, + LessThanEqual, + GreaterThan, + GreaterThanEqual, +} diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index a5e0a6d95c..d9f525f39f 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -170,6 +170,9 @@ Never share your secret api keys. Keep them guarded and secure. routes::routing::toggle_elimination_routing, routes::routing::contract_based_routing_setup_config, routes::routing::contract_based_routing_update_configs, + routes::routing::call_decide_gateway_open_router, + routes::routing::call_update_gateway_score_open_router, + routes::routing::evaluate_routing_rule, // Routes for blocklist routes::blocklist::remove_entry_from_blocklist, @@ -809,6 +812,22 @@ Never share your secret api keys. Keep them guarded and secure. api_models::authentication::ThreeDsData, api_models::authentication::AuthenticationEligibilityRequest, api_models::authentication::AuthenticationEligibilityResponse, + api_models::open_router::OpenRouterDecideGatewayRequest, + api_models::open_router::DecideGatewayResponse, + api_models::open_router::UpdateScorePayload, + api_models::open_router::UpdateScoreResponse, + api_models::routing::RoutingEvaluateRequest, + api_models::routing::RoutingEvaluateResponse, + api_models::routing::ValueType, + api_models::routing::DeRoutableConnectorChoice, + api_models::routing::RoutableConnectorChoice, + api_models::open_router::PaymentInfo, + common_utils::id_type::PaymentId, + common_utils::id_type::ProfileId, + api_models::open_router::RankingAlgorithm, + api_models::open_router::TxnStatus, + api_models::open_router::PriorityLogicOutput, + api_models::open_router::PriorityLogicData, api_models::user::PlatformAccountCreateRequest, api_models::user::PlatformAccountCreateResponse, )), diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 78f4c8b187..234508de37 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -386,3 +386,69 @@ pub async fn contract_based_routing_setup_config() {} security(("api_key" = []), ("jwt_key" = [])) )] pub async fn contract_based_routing_update_configs() {} + +#[cfg(feature = "v1")] +/// Routing - Evaluate +/// +/// Evaluate routing rules +#[utoipa::path( + post, + path = "/routing/evaluate", + request_body = OpenRouterDecideGatewayRequest, + responses( + (status = 200, description = "Routing rules evaluated successfully", body = DecideGatewayResponse), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Evaluate routing rules", + security(("api_key" = [])) +)] +pub async fn call_decide_gateway_open_router() {} + +#[cfg(feature = "v1")] +/// Routing - Feedback +/// +/// Update gateway scores for dynamic routing +#[utoipa::path( + post, + path = "/routing/feedback", + request_body = UpdateScorePayload, + responses( + (status = 200, description = "Gateway score updated successfully", body = UpdateScoreResponse), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Update gateway scores", + security(("api_key" = [])) +)] +pub async fn call_update_gateway_score_open_router() {} + +#[cfg(feature = "v1")] +/// Routing - Rule Evaluate +/// +/// Evaluate routing rules +#[utoipa::path( + post, + path = "/routing/rule/evaluate", + request_body = RoutingEvaluateRequest, + responses( + (status = 200, description = "Routing rules evaluated successfully", body = RoutingEvaluateResponse), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Evaluate routing rules (alternative)", + security(("api_key" = [])) +)] +pub async fn evaluate_routing_rule() {} diff --git a/crates/router/src/core/payments/routing/utils.rs b/crates/router/src/core/payments/routing/utils.rs index 5eb929a92d..d9d5ed1de2 100644 --- a/crates/router/src/core/payments/routing/utils.rs +++ b/crates/router/src/core/payments/routing/utils.rs @@ -3,7 +3,9 @@ use std::collections::{HashMap, HashSet}; use api_models::{ open_router as or_types, routing::{ - self as api_routing, ConnectorSelection, ConnectorVolumeSplit, RoutableConnectorChoice, + self as api_routing, ComparisonType, ConnectorSelection, ConnectorVolumeSplit, + DeRoutableConnectorChoice, MetadataValue, NumberComparison, RoutableConnectorChoice, + RoutingEvaluateRequest, RoutingEvaluateResponse, ValueType, }, }; use async_trait::async_trait; @@ -683,99 +685,6 @@ impl DecisionEngineErrorsInterface for or_types::ErrorResponse { } } -//TODO: temporary change will be refactored afterwards -#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq)] -pub struct RoutingEvaluateRequest { - pub created_by: String, - pub parameters: HashMap>, - pub fallback_output: Vec, -} -impl common_utils::events::ApiEventMetric for RoutingEvaluateRequest {} - -#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] -pub struct RoutingEvaluateResponse { - pub status: String, - pub output: serde_json::Value, - #[serde(deserialize_with = "deserialize_connector_choices")] - pub evaluated_output: Vec, - #[serde(deserialize_with = "deserialize_connector_choices")] - pub eligible_connectors: Vec, -} -impl common_utils::events::ApiEventMetric for RoutingEvaluateResponse {} -/// Routable Connector chosen for a payment -#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)] -pub struct DeRoutableConnectorChoice { - pub gateway_name: common_enums::RoutableConnectors, - pub gateway_id: Option, -} - -fn deserialize_connector_choices<'de, D>( - deserializer: D, -) -> Result, D::Error> -where - D: serde::Deserializer<'de>, -{ - let infos = Vec::::deserialize(deserializer)?; - Ok(infos - .into_iter() - .map(RoutableConnectorChoice::from) - .collect()) -} - -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct MetadataValue { - pub key: String, - pub value: String, -} - -/// Represents a value in the DSL -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(tag = "type", content = "value", rename_all = "snake_case")] -pub enum ValueType { - /// Represents a number literal - Number(u64), - /// Represents an enum variant - EnumVariant(String), - /// Represents a Metadata variant - MetadataVariant(MetadataValue), - /// Represents a arbitrary String value - StrValue(String), - /// Represents a global reference, which is a reference to a global variable - GlobalRef(String), - /// Represents an array of numbers. This is basically used for - /// "one of the given numbers" operations - /// eg: payment.method.amount = (1, 2, 3) - NumberArray(Vec), - /// Similar to NumberArray but for enum variants - /// eg: payment.method.cardtype = (debit, credit) - EnumVariantArray(Vec), - /// Like a number array but can include comparisons. Useful for - /// conditions like "500 < amount < 1000" - /// eg: payment.amount = (> 500, < 1000) - NumberComparisonArray(Vec), -} - -pub type Metadata = HashMap; -/// Represents a number comparison for "NumberComparisonArrayValue" -#[derive(Clone, Debug, Eq, Hash, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub struct NumberComparison { - pub comparison_type: ComparisonType, - pub number: u64, -} - -/// Conditional comparison type -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] -pub enum ComparisonType { - Equal, - NotEqual, - LessThan, - LessThanEqual, - GreaterThan, - GreaterThanEqual, -} - /// Represents a single comparison condition. #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] @@ -875,16 +784,6 @@ impl ConnectorInfo { } } -impl From for RoutableConnectorChoice { - fn from(choice: DeRoutableConnectorChoice) -> Self { - Self { - choice_kind: api_routing::RoutableChoiceKind::FullStruct, - connector: choice.gateway_name, - merchant_connector_id: choice.gateway_id, - } - } -} - #[derive(Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Output { @@ -896,6 +795,7 @@ pub enum Output { pub type Globals = HashMap>; +pub type Metadata = HashMap; /// The program, having a default connector selection and /// a bunch of rules. Also can hold arbitrary metadata. #[derive(Clone, Debug, Serialize, Deserialize)] diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index ffd5c265db..53f6a75c5c 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -6,7 +6,10 @@ use actix_web::{web, HttpRequest, Responder}; use api_models::{ enums, - routing::{self as routing_types, RoutingRetrieveQuery}, + routing::{ + self as routing_types, RoutingEvaluateRequest, RoutingEvaluateResponse, + RoutingRetrieveQuery, + }, }; use error_stack::ResultExt; use hyperswitch_domain_models::merchant_context::MerchantKeyStore; @@ -19,10 +22,7 @@ use router_env::{ use crate::{ core::{ api_locking, conditional_config, - payments::routing::utils::{ - DecisionEngineApiHandler, EuclidApiClient, RoutingEvaluateRequest, - RoutingEvaluateResponse, - }, + payments::routing::utils::{DecisionEngineApiHandler, EuclidApiClient}, routing, surcharge_decision_config, }, db::errors::StorageErrorExt,