Skip to content

Conversation

prajjwalkumar17
Copy link
Member

@prajjwalkumar17 prajjwalkumar17 commented May 22, 2025

Type of Change

  • Bugfix
  • New feature
  • Enhancement
  • Refactoring
  • Dependency updates
  • Documentation
  • CI/CD

Description

This PR introduces support for listing both static and dynamic routing algorithms under a unified API interface. To accomplish this, it refactors how routing algorithms are represented and parsed, introducing clear separation between StaticRoutingAlgorithm and DynamicRoutingAlgorithm. The new enum RoutingAlgorithmWrapper is used to encapsulate both types.

Dynamic routing strategies like EliminationBasedAlgorithm, SuccessBasedAlgorithm, and ContractBasedAlgorithm are now modeled and tracked similarly to static rules, allowing for consistent listing and configuration.

Outcomes

  • Enables unified listing of static and dynamic routing algorithms linked to a business profile.
  • Enhances the routing engine’s flexibility to support multiple dynamic strategies (e.g., success-based, elimination-based).
  • Provides type-safe and structured handling of dynamic routing configurations.
  • Prepares the system for future expansion (e.g., integration of new algorithm types).
  • Improves API and database alignment by enabling dynamic routing references per profile.

Diff Hunk Explanation

api_models/src/routing.rs

  • Added StaticRoutingAlgorithm, DynamicRoutingAlgorithm, and RoutingAlgorithmWrapper enums to distinguish algorithm types.
  • Updated various structs to use StaticRoutingAlgorithm instead of the generic RoutingAlgorithm.
  • Introduced serde wrappers with #[serde(untagged)] for backward-compatible JSON parsing.

api_models/src/events/routing.rs

  • Registered new types like RoutingVolumeSplit for event logging via ApiEventMetric.

router/src/core/routing.rs

  • In retrieve_linked_routing_config, extended logic to resolve dynamic routing references and list their metadata.
  • Dynamic algorithms from the profile are resolved and listed alongside static algorithms.

router/src/core/routing/helpers.rs

  • Updated connector validation to work with StaticRoutingAlgorithm.
  • Added support to validate connectors within dynamic strategies indirectly via wrapper types.

router/src/core/payments/routing.rs

  • Adjusted runtime routing execution to handle StaticRoutingAlgorithm.
  • Ensured routing output from interpreters is correctly matched and handled.

transformers.rs, admin.rs, payments.rs, and stripe/types.rs

  • Refactored references and conversions to use the newly introduced StaticRoutingAlgorithm.

Additional Changes

  • This PR modifies the API contract
  • This PR modifies the database schema
  • This PR modifies application configuration/environment variables

Motivation and Context

How did you test it?

Curls for testing the flows:

  1. Create a static rule:
curl --location 'http://127.0.0.1:8080/routing' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu' \
--data '
{
    "name": "Priority rule with fallback",
    "description": "It is my ADVANCED config",
    "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
    "algorithm": {
        "type": "advanced", 
        "data": {
            "defaultSelection": {
                "type": "priority",
                "data": [
                    {
                        "connector": "stripe",
                        "merchant_connector_id": "mca_gtMC3BG1m6YTX0j8hwUU"
                    }
                ]
            },
            "rules": [
                {
                    "name": "cybersource first",
                    "connectorSelection": {
                        "type": "priority",
                        "data": [
                            {
                                "connector": "stripe",
                                "merchant_connector_id": "mca_gtMC3BG1m6YTX0j8hwUU"
                            }
                        ]
                    },
                    "statements": [
                        {
                            "condition": [
                                {
                                    "lhs": "billing_country",
                                    "comparison": "equal",
                                    "value": {
                                        "type": "enum_variant",
                                        "value": "Netherlands"
                                    },
                                    "metadata": {}
                                },
                                {
                                "lhs": "amount",
                                "comparison": "greater_than",
                                "value": {
                                    "type": "number",
                                    "value": 1000
                                },
                                "metadata": {}
                            }
                            ],
                            "nested": null
                        }
                    ]
                }
            ],
            "metadata": {}
        }
    }
}
'
  1. Retrive the rule using id
curl --location 'http://127.0.0.1:8080/routing/routing_v2x2BWS0Rf3QKsyc8GUU' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response

{
    "id": "routing_v2x2BWS0Rf3QKsyc8GUU",
    "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
    "name": "Priority rule with fallback",
    "description": "It is my ADVANCED config",
    "algorithm": {
        "type": "advanced",
        "data": {
            "defaultSelection": {
                "type": "priority",
                "data": [
                    {
                        "connector": "stripe",
                        "merchant_connector_id": "mca_gtMC3BG1m6YTX0j8hwUU"
                    }
                ]
            },
            "rules": [
                {
                    "name": "cybersource first",
                    "connectorSelection": {
                        "type": "priority",
                        "data": [
                            {
                                "connector": "stripe",
                                "merchant_connector_id": "mca_gtMC3BG1m6YTX0j8hwUU"
                            }
                        ]
                    },
                    "statements": [
                        {
                            "condition": [
                                {
                                    "lhs": "billing_country",
                                    "comparison": "equal",
                                    "value": {
                                        "type": "enum_variant",
                                        "value": "Netherlands"
                                    },
                                    "metadata": {}
                                },
                                {
                                    "lhs": "amount",
                                    "comparison": "greater_than",
                                    "value": {
                                        "type": "number",
                                        "value": 1000
                                    },
                                    "metadata": {}
                                }
                            ],
                            "nested": null
                        }
                    ]
                }
            ],
            "metadata": {}
        }
    },
    "created_at": 1747996597,
    "modified_at": 1747996597,
    "algorithm_for": "payment"
}
  1. Toggle SR routing
curl --location --request POST 'http://localhost:8080/account/merchant_1747967454/business_profile/pro_INSNcGnV7dSldWXpW1GE/dynamic_routing/success_based/toggle?enable=dynamic_connector_selection' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'
  1. Retrieve it using the routing id:
curl --location 'http://127.0.0.1:8080/routing/routing_XP894HcdwYPJqCQjQqJV' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response:

{
    "id": "routing_XP894HcdwYPJqCQjQqJV",
    "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
    "name": "Success rate based dynamic routing algorithm",
    "description": "",
    "algorithm": {
        "params": [
            "PaymentMethod"
        ],
        "config": {
            "min_aggregates_size": 2,
            "default_success_rate": 100.0,
            "max_aggregates_size": 3,
            "current_block_threshold": {
                "duration_in_mins": 5,
                "max_total_count": 2
            },
            "specificity_level": "merchant"
        }
    },
    "created_at": 1747967494,
    "modified_at": 1747967494,
    "algorithm_for": "payment"
}
  1. Toggle elimination routing
curl --location --request POST 'http://localhost:8080/account/merchant_1747967454/business_profile/pro_INSNcGnV7dSldWXpW1GE/dynamic_routing/elimination/toggle?enable=dynamic_connector_selection' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'
  1. Retrieve it using the routing id:
curl --location 'http://127.0.0.1:8080/routing/routing_XP894HcdwYPJqCQjQqJV' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response:

{
    "id": "routing_ZlQZoyVildA7HusIfehn",
    "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
    "name": "Elimination based dynamic routing algorithm",
    "description": "",
    "algorithm": {
        "params": [
            "PaymentMethod"
        ],
        "elimination_analyser_config": {
            "bucket_size": 5,
            "bucket_leak_interval_in_secs": 60
        }
    },
    "created_at": 1747968281,
    "modified_at": 1747968281,
    "algorithm_for": "payment"
}
  1. Toggle contract based routing
curl --location 'http://localhost:8080/account/merchant_1747967454/business_profile/pro_INSNcGnV7dSldWXpW1GE/dynamic_routing/contracts/toggle?enable=dynamic_connector_selection' \
--header 'Content-Type: application/json' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu' \
--data '
{
    "config": null,
    "label_info": null
}'
  1. Retrieve it using the routing id:
curl --location 'http://127.0.0.1:8080/routing/routing_XP894HcdwYPJqCQjQqJV' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response:

{
    "id": "routing_C13lGF4NR613rAyuUsuZ",
    "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
    "name": "Contract based dynamic routing algorithm",
    "description": "",
    "algorithm": {
        "config": null,
        "label_info": null
    },
    "created_at": 1747998023,
    "modified_at": 1747998023,
    "algorithm_for": "payment"
}
  1. Volume between routing types
curl --location --request POST 'http://localhost:8080/account/merchant_1747967454/business_profile/pro_INSNcGnV7dSldWXpW1GE/dynamic_routing/set_volume_split?split=100' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response

{
    "routing_type": "dynamic",
    "split": 100
}
  1. List active configs
    toggle SR, toggle ER
    create a static rule and activate it
curl --location 'http://localhost:8080/routing/active' \
--header 'api-key: dev_IpYFqCd4DHf7x4ER1GBQrW0mHOZzam3pYyuP2tSRb6q7qvTw7jvEjF7UzjwFPXWu'

Response
3 values should come

[
    {
        "id": "routing_j9omIKybJb6jw2PIwFhQ",
        "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
        "name": "Priority rule with fallback",
        "kind": "advanced",
        "description": "It is my ADVANCED config",
        "created_at": 1748005058,
        "modified_at": 1748005058,
        "algorithm_for": "payment",
        "decision_engine_routing_id": null
    },
    {
        "id": "routing_ZlQZoyVildA7HusIfehn",
        "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
        "name": "Elimination based dynamic routing algorithm",
        "kind": "dynamic",
        "description": "",
        "created_at": 1747968281,
        "modified_at": 1747968281,
        "algorithm_for": "payment",
        "decision_engine_routing_id": null
    },
    {
        "id": "routing_4E6HvDyeOUJKs3ncGDk7",
        "profile_id": "pro_INSNcGnV7dSldWXpW1GE",
        "name": "Contract based dynamic routing algorithm",
        "kind": "dynamic",
        "description": "",
        "created_at": 1748005049,
        "modified_at": 1748005049,
        "algorithm_for": "payment",
        "decision_engine_routing_id": null
    }
]

Checklist

  • I formatted the code cargo +nightly fmt --all
  • I addressed lints thrown by cargo clippy
  • I reviewed the submitted code
  • I added unit tests for my changes where possible

@prajjwalkumar17 prajjwalkumar17 requested review from a team as code owners May 22, 2025 11:13
Copy link

semanticdiff-com bot commented May 22, 2025

@Sarthak1799 Sarthak1799 requested a review from a team as a code owner May 22, 2025 11:30
@GauravRawat369 GauravRawat369 linked an issue May 23, 2025 that may be closed by this pull request
@prajjwalkumar17 prajjwalkumar17 requested review from a team as code owners May 23, 2025 10:45
@prajjwalkumar17 prajjwalkumar17 self-assigned this May 23, 2025
…/hyperswitch into feat_dynamic_routing_requirements
Comment on lines +1203 to +1231
let dynamic_routing_ref: routing_types::DynamicRoutingAlgorithmRef = business_profile
.dynamic_routing_algorithm
.clone()
.map(|val| val.parse_value("DynamicRoutingAlgorithmRef"))
.transpose()
.change_context(errors::ApiErrorResponse::InternalServerError)
.attach_printable(
"unable to deserialize dynamic routing algorithm ref from business profile",
)?
.unwrap_or_default();

// Collect all dynamic algorithm IDs
let mut dynamic_algorithm_ids = Vec::new();

if let Some(sba) = &dynamic_routing_ref.success_based_algorithm {
if let Some(id) = &sba.algorithm_id_with_timestamp.algorithm_id {
dynamic_algorithm_ids.push(id.clone());
}
}
if let Some(era) = &dynamic_routing_ref.elimination_routing_algorithm {
if let Some(id) = &era.algorithm_id_with_timestamp.algorithm_id {
dynamic_algorithm_ids.push(id.clone());
}
}
if let Some(cbr) = &dynamic_routing_ref.contract_based_routing {
if let Some(id) = &cbr.algorithm_id_with_timestamp.algorithm_id {
dynamic_algorithm_ids.push(id.clone());
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be made parallel

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure this can be taken up in another PR.
cc: @GauravRawat369

Chethan-rao
Chethan-rao previously approved these changes May 23, 2025
@hyperswitch-bot hyperswitch-bot bot added the M-api-contract-changes Metadata: This PR involves API contract changes label May 23, 2025
Chethan-rao
Chethan-rao previously approved these changes May 23, 2025
Copy link
Contributor

@sahkal sahkal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compatibility api changes LGTM!

@likhinbopanna likhinbopanna added this pull request to the merge queue May 27, 2025
Merged via the queue into main with commit a654695 May 27, 2025
15 of 20 checks passed
@likhinbopanna likhinbopanna deleted the feat_dynamic_routing_requirements branch May 27, 2025 10:32
pixincreate added a commit that referenced this pull request May 30, 2025
…ordea-sepa

* 'main' of github.com:juspay/hyperswitch: (30 commits)
  chore(version): 2025.05.30.0
  chore(ci): update postman ci credentials (#8172)
  chore(docs): remove old add_connector.md file (#8143)
  refactor: Payment Attempt as mandatory field in PaymentStatusData (#8126)
  fix(payment_link): sanitize embedded payment link data (#7736)
  chore(version): 2025.05.29.0
  feat(analytics): Add ckh columns for 3ds intelligence analytics (#8136)
  refactor(debit_routing): Handle missing merchant_business_country by defaulting to US (#8141)
  chore(version): 2025.05.28.0
  refactor(success_based): add support for exploration (#8158)
  feat(dynamic_routing): add get api for dynamic routing volume split (#8114)
  fix: incorrect payout_method_id in payouts table (#8107)
  feat(router): Enable client_secret auth for payments_get_intent [v2] (#8119)
  chore: address Rust 1.87.0 clippy lints (#8130)
  feat: list for dynamic routing (#8111)
  ci(cypress): fix mandates unsupported connectors (#8086)
  feat(connector): [Barclaycard] Implement Cards - Non 3DS flow (#8068)
  fix(authentication): add Organization context validation in `Merchant Create` and `Merchant List` APIs (#8103)
  feat(connector): [Worldpayxml] add card payment (#8076)
  feat(connector): Stripe revolut pay wallet integration (#8066)
  ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
M-api-contract-changes Metadata: This PR involves API contract changes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

feat: list for dynamic routing
7 participants