Skip to content

feat(routing): add support to handle gateway latency during SR routing#126

Merged
PriyanshuC132 merged 5 commits into
mainfrom
txn-latency-routing
Jul 31, 2025
Merged

feat(routing): add support to handle gateway latency during SR routing#126
PriyanshuC132 merged 5 commits into
mainfrom
txn-latency-routing

Conversation

@jagan-jaya
Copy link
Copy Markdown
Collaborator

@jagan-jaya jagan-jaya commented Jul 28, 2025

Context

Merchants usually have a time limit for the order create call, if the gateway takes more time than a predefined value merchant will consider the request as stale and mark the transaction as PENDING_VBV. To avoid this situation its better to ignore those processors which are having high order response time.

As part of the existing SR routing algo we are adding support to handle this latency check by which we can pass the time taken by the gateway during the order create call. Our SR logic will skip rewarding the processor if it breaches the merchant configured threshold.

Tests

  1. create merchant account
curl --location 'http://localhost:8080/merchant-account/create' \
--header 'Content-Type: application/json' \
--data '{
  "merchant_id": "merc_1753964796"  
}'

Response

"Merchant account created successfully"
  1. Create SR rule
curl --location 'http://localhost:8080/rule/create' \
--header 'Content-Type: application/json' \
--data '{
    "merchant_id": "merc_1753964795",
    "config": {
      "type": "successRate",
      "data": {
        "defaultLatencyThreshold": 90,
        "defaultSuccessRate": 0.5,
        "defaultBucketSize": 200,
        "defaultHedgingPercent": 5,
        "subLevelInputConfig": [
          {
            "paymentMethodType": "upi",
            "paymentMethod": "upi_collect",
            "bucketSize": 250,
            "hedgingPercent": 1
          }
        ]
      }
    }
  }'

Response

"Success Rate Configuration created successfully"
  1. Create Elimination Rule with SR threshold of 0.75 and maxLatency of 5000 millis
curl --location 'http://localhost:8080/rule/create' \
--header 'Content-Type: application/json' \
--data '{
    "merchant_id": "merc_1753964795",
    "config": {
        "type": "elimination",
        "data": {
            "threshold": 0.75,
            "txnLatency": {
                "gatewayLatency": 5000
            }
        }
    }
}'

Response

"Elimination Configuration created successfully"
  1. Decide gateway
curl --location 'http://localhost:8080/decide-gateway' \
--header 'Content-Type: application/json' \
--data '{           
        "merchantId": "merc_1753964795",
        "eligibleGatewayList": ["GatewayA", "GatewayB", "GatewayC"],
        "rankingAlgorithm": "SR_BASED_ROUTING",
        "eliminationEnabled": true,
        "paymentInfo": {
            "paymentId": "PAY_1753965083",
            "amount": 100.50,
            "currency": "USD",
            "customerId": "CUST12345",
            "udfs": null,
            "preferredGateway": null,
            "paymentType": "ORDER_PAYMENT",
            "metadata": null,
            "internalMetadata": null,
            "isEmi": false,
            "emiBank": null,
            "emiTenure": null,
            "paymentMethodType": "UPI",
            "paymentMethod": "UPI_PAY",
            "paymentSource": null,
            "authType": null,
            "cardIssuerBankName": null,
            "cardIsin": null,
            "cardType": null,
            "cardSwitchProvider": null
        }
}'

Response

{
    "decided_gateway": "GatewayC",
    "gateway_priority_map": {
        "GatewayA": 1.0,
        "GatewayB": 1.0,
        "GatewayC": 1.0
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayA",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}
  1. Update gateway score of chosen connector as failure to see if the connector score reduces
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayC",
  "gatewayReferenceId": null,
  "status": "FAILURE",
  "paymentId": "PAY_1753965080",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 4000
  }
}'

Response

{
    "message": "Success"
}
  1. Call decide gateway again to check the current gateway scores
curl --location 'http://localhost:8080/decide-gateway' \
--header 'Content-Type: application/json' \
--data '{           
        "merchantId": "merc_1753964795",
        "eligibleGatewayList": ["GatewayA", "GatewayB", "GatewayC"],
        "rankingAlgorithm": "SR_BASED_ROUTING",
        "eliminationEnabled": true,
        "paymentInfo": {
            "paymentId": "PAY_1753966081",
            "amount": 100.50,
            "currency": "USD",
            "customerId": "CUST12345",
            "udfs": null,
            "preferredGateway": null,
            "paymentType": "ORDER_PAYMENT",
            "metadata": null,
            "internalMetadata": null,
            "isEmi": false,
            "emiBank": null,
            "emiTenure": null,
            "paymentMethodType": "UPI",
            "paymentMethod": "UPI_PAY",
            "paymentSource": null,
            "authType": null,
            "cardIssuerBankName": null,
            "cardIsin": null,
            "cardType": null,
            "cardSwitchProvider": null
        }
}'

Response

{
    "decided_gateway": "GatewayA",
    "gateway_priority_map": {
        "GatewayC": 0.995,
        "GatewayA": 1.0,
        "GatewayB": 1.0
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayB",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}

As expected score of "GatewayC": 0.995 is reduced

  1. Now call update-score for gatewayA with status: success
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayA",
  "gatewayReferenceId": null,
  "status": "AUTHORIZED",
  "paymentId": "PAY_1753965291",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 4000
  }
}'

Response

{
    "message": "Success"
}
  1. Call decide gateway to check current scores
curl --location 'http://localhost:8080/decide-gateway' \
--header 'Content-Type: application/json' \
--data '{           
        "merchantId": "merc_1753964795",
        "eligibleGatewayList": ["GatewayA", "GatewayB", "GatewayC"],
        "rankingAlgorithm": "SR_BASED_ROUTING",
        "eliminationEnabled": true,
        "paymentInfo": {
            "paymentId": "PAY_1753966335",
            "amount": 100.50,
            "currency": "USD",
            "customerId": "CUST12345",
            "udfs": null,
            "preferredGateway": null,
            "paymentType": "ORDER_PAYMENT",
            "metadata": null,
            "internalMetadata": null,
            "isEmi": false,
            "emiBank": null,
            "emiTenure": null,
            "paymentMethodType": "UPI",
            "paymentMethod": "UPI_PAY",
            "paymentSource": null,
            "authType": null,
            "cardIssuerBankName": null,
            "cardIsin": null,
            "cardType": null,
            "cardSwitchProvider": null
        }
}'

Response

{
    "decided_gateway": "GatewayB",
    "gateway_priority_map": {
        "GatewayC": 0.995,
        "GatewayA": 1.0,
        "GatewayB": 1.0
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayA",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}

Score of gatewayA remains unchanged

  1. Now let's try to send update-gateway-score with status: pending_vbv to make sure temporary penalty is applied for gatewayA
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayA",
  "gatewayReferenceId": null,
  "status": "PENDING_VBV",
  "paymentId": "PAY_1753967334",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 4000
  }
}'

Check redis to see if the gateway is penalized

127.0.0.1:6379> get gw_score_merc_1753964795_ORDER_PAYMENT_GatewayA_UPI_UPI_PAY
"{\"merchants\":null,\"score\":0.95,\"timestamp\":1753966175558,\"lastResetTimestamp\":1753966175558,\"transactionCount\":1}"
  1. Call update-gateway-score with status: success to restore the elimination gateway score
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayA",
  "gatewayReferenceId": null,
  "status": "AUTHORIZED",
  "paymentId": "PAY_1753967334",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 4000
  }
}'

Check redis to see if the score is rewarded back

127.0.0.1:6379> get gw_score_merc_1753964795_ORDER_PAYMENT_GatewayA_UPI_UPI_PAY
"{\"merchants\":null,\"score\":0.9999699999999999,\"timestamp\":1753966175558,\"lastResetTimestamp\":1753966175558,\"transactionCount\":1}"
  1. Decide gateway again to get current gateways scores and now check if elimination penalty is applied but not rewarded back due to txnLatency above configured limit
curl --location 'http://localhost:8080/decide-gateway' \
--header 'Content-Type: application/json' \
--data '{           
        "merchantId": "merc_1753964795",
        "eligibleGatewayList": ["GatewayA", "GatewayB", "GatewayC"],
        "rankingAlgorithm": "SR_BASED_ROUTING",
        "eliminationEnabled": true,
        "paymentInfo": {
            "paymentId": "PAY_1753967661",
            "amount": 100.50,
            "currency": "USD",
            "customerId": "CUST12345",
            "udfs": null,
            "preferredGateway": null,
            "paymentType": "ORDER_PAYMENT",
            "metadata": null,
            "internalMetadata": null,
            "isEmi": false,
            "emiBank": null,
            "emiTenure": null,
            "paymentMethodType": "UPI",
            "paymentMethod": "UPI_PAY",
            "paymentSource": null,
            "authType": null,
            "cardIssuerBankName": null,
            "cardIsin": null,
            "cardType": null,
            "cardSwitchProvider": null
        }
}'

Response

{
    "decided_gateway": "GatewayB",
    "gateway_priority_map": {
        "GatewayB": 0.995,
        "GatewayA": 0.99,
        "GatewayC": 0.995
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayC",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}
  1. Call update-gateway-score with Pending_vbv to see if the score is reduced
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayB",
  "gatewayReferenceId": null,

  "status": "PENDING_VBV",

  "paymentId": "PAY_1753967655",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 4000
  }
}'

check Redis

127.0.0.1:6379> get gw_score_merc_1753964795_ORDER_PAYMENT_GatewayB_UPI_UPI_PAY
"{\"merchants\":null,\"score\":0.95,\"timestamp\":1753967704198,\"lastResetTimestamp\":1753967704198,\"transactionCount\":1}"
  1. Again call update-gateway-score to see that the elimination score remains same, as the txnLatency exceeds 5secs it won't be rewarded back
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayB",
  "gatewayReferenceId": null,
  "status": "AUTHORIZED",
  "paymentId": "PAY_1753967655",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 6000
  }
}'

Check redis and logs to see if the score is unchanged

LOGS

2025-07-31T13:15:43.844545Z  INFO  Latency & Threshold: Some(6000.0) Some(5000.0), action: "txn_latency_within_threshold", tag: "txn_latency_within_threshold"
    at src/feedback/gateway_scoring_service.rs:274
    
2025-07-31T13:15:43.845362Z DEBUG  Update GW Score call received outside Update Window, tag: "updateGatewayScore", action: "updateGatewayScore"
    at src/feedback/gateway_scoring_service.rs:721
    in request with method: POST, uri: /update-gateway-score, version: HTTP/1.1, is_audit_trail_log: "true", schema_version: "V2", tenant_name: "JUSPAY", tenant_id: "JUSPAY", tag: "euler_logs", config_version: 4872
    
  REDIS  
127.0.0.1:6379> get gw_score_merc_1753964795_ORDER_PAYMENT_GatewayB_UPI_UPI_PAY
"{\"merchants\":null,\"score\":0.95,\"timestamp\":1753967704198,\"lastResetTimestamp\":1753967704198,\"transactionCount\":1}"

Score remains unchanged as we don't update the gateway score when txnLatency is above configured threshold

  1. call decide-gateway again to check the current scores
{
    "decided_gateway": "GatewayB",
    "gateway_priority_map": {
        "GatewayA": 0.99,
        "GatewayC": 0.995,
        "GatewayB": 0.995
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayC",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}

Response

{
    "decided_gateway": "GatewayB",
    "gateway_priority_map": {
        "GatewayA": 0.99,
        "GatewayC": 0.995,
        "GatewayB": 0.995
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_SELECTION_V3_ROUTING",
    "gateway_before_evaluation": "GatewayC",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}
  1. Change elimination threshold to higher limit to see if the connector is eliminated in next call
curl --location 'http://localhost:8080/rule/create' \
--header 'Content-Type: application/json' \
--data '{
    "merchant_id": "merc_1753964795",
    "config": {
        "type": "elimination",
        "data": {
            "threshold": 0.999,
            "txnLatency": {
                "gatewayLatency": 5000
            }
        }
    }
}'
  1. call update-gateway-score to reduce gatewayb score
curl --location 'http://localhost:8080/update-gateway-score' \
--header 'Content-Type: application/json' \
--data '{
  "merchantId" : "merc_1753964795",
  "gateway": "GatewayB",
  "gatewayReferenceId": null,

  "status": "PENDING_VBV",

  "paymentId": "PAY_1753968317",
  "enforceDynamicRoutingFailure" : null,
  "txnLatency": {
    "gatewayLatency": 6000
  }
}'
  1. call decide gateway to check if gatewayB is ignored
curl --location 'http://localhost:8080/decide-gateway' \
--header 'Content-Type: application/json' \
--data '{           
        "merchantId": "merc_1753964795",
        "eligibleGatewayList": ["GatewayA", "GatewayB", "GatewayC"],
        "rankingAlgorithm": "SR_BASED_ROUTING",
        "eliminationEnabled": true,
        "paymentInfo": {
            "paymentId": "PAY_1753968454",
            "amount": 100.50,
            "currency": "USD",
            "customerId": "CUST12345",
            "udfs": null,
            "preferredGateway": null,
            "paymentType": "ORDER_PAYMENT",
            "metadata": null,
            "internalMetadata": null,
            "isEmi": false,
            "emiBank": null,
            "emiTenure": null,
            "paymentMethodType": "UPI",
            "paymentMethod": "UPI_PAY",
            "paymentSource": null,
            "authType": null,
            "cardIssuerBankName": null,
            "cardIsin": null,
            "cardType": null,
            "cardSwitchProvider": null
        }
}'

Response

{
    "decided_gateway": "GatewayC",
    "gateway_priority_map": {
        "GatewayB": 0.199,
        "GatewayC": 0.995,
        "GatewayA": 0.99
    },
    "filter_wise_gateways": null,
    "priority_logic_tag": null,
    "routing_approach": "SR_V3_DOWNTIME_ROUTING",
    "gateway_before_evaluation": "GatewayC",
    "priority_logic_output": {
        "isEnforcement": false,
        "gws": [
            "GatewayA",
            "GatewayB",
            "GatewayC"
        ],
        "priorityLogicTag": null,
        "gatewayReferenceIds": {},
        "primaryLogic": null,
        "fallbackLogic": null
    },
    "debit_routing_output": null,
    "reset_approach": "NO_RESET",
    "routing_dimension": "ORDER_PAYMENT, UPI, UPI_PAY",
    "routing_dimension_level": "PM_LEVEL",
    "is_scheduled_outage": false,
    "is_dynamic_mga_enabled": false,
    "gateway_mga_id_map": null,
    "is_rust_based_decider": true
}

Run cypress tests with npm run test
image

This pull request introduces changes to incorporate transaction latency (txnLatency) as a new field in multiple structures and functions, enabling latency-based decision-making in routing and scoring logic. Key updates include adding the txnLatency field, defining a new OrchestrationLatencyThreshold struct, and implementing logic to penalize transactions exceeding latency thresholds.

Enhancements to data structures:

  • src/decider/gatewaydecider/types.rs: Added txnLatency to DomainDeciderRequestForApiCallV2 and OrchestrationLatencyThreshold to SrV3InputConfig to support latency-based routing. Defined the OrchestrationLatencyThreshold struct to configure latency thresholds for gateways. [1] [2] [3]

  • src/types/txn_details/types.rs: Added txnLatency as an optional field in TxnDetail to capture transaction latency in milliseconds.

  • src/feedback/types.rs: Updated UpdateScorePayload to include txnLatency, and made Milliseconds cloneable for reuse. [1] [2]

Routing and scoring logic updates:

  • src/feedback/gateway_scoring_service.rs: Introduced isLatencyAboveConfiguredThreshold to compare transaction latency against thresholds. Modified getGatewayScoringType to penalize transactions exceeding latency thresholds by returning GatewayScoringType::PENALISE_SRV3. [1] [2] [3]

Parsing and utility updates:

Additional changes:

@jagan-jaya jagan-jaya self-assigned this Jul 29, 2025
@jagan-jaya jagan-jaya marked this pull request as ready for review July 29, 2025 13:14
Copilot AI review requested due to automatic review settings July 29, 2025 13:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR adds support for handling gateway latency during Success Rate (SR) routing by introducing transaction latency tracking and penalization logic. The implementation allows merchants to configure latency thresholds and automatically penalizes gateways that exceed these thresholds during scoring.

  • Introduces txnLatency field across multiple data structures to capture gateway response times
  • Implements latency threshold checking in SR routing algorithm to skip rewarding slow gateways
  • Adds comprehensive Cypress test framework for validating routing flows and latency-based scoring

Reviewed Changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
src/types/txn_details/types.rs Added txnLatency field to TxnDetail and defined TransactionLatency struct
src/types/routing_configuration.rs Added txn_latency field to SuccessRateData with TransactionLatencyThreshold struct
src/feedback/types.rs Updated UpdateScorePayload with txnLatency and made Milliseconds cloneable
src/feedback/gateway_scoring_service.rs Implemented latency threshold checking and penalization logic in scoring
src/feedback/utils.rs Modified to populate txnLatency from API payload
src/decider/gatewaydecider/validators.rs Set txnLatency to None in parser function
src/decider/gatewaydecider/types.rs Added txnLatency field to structs and defined threshold configuration
package.json Added Cypress testing framework configuration
cypress/* Complete test framework for routing flows validation

Comment thread src/types/txn_details/types.rs
Comment thread src/types/txn_details/types.rs
Comment thread src/feedback/gateway_scoring_service.rs Outdated
Comment thread src/feedback/utils.rs Outdated
Comment thread src/types/routing_configuration.rs Outdated
Comment thread src/decider/gatewaydecider/types.rs
@PriyanshuC132 PriyanshuC132 merged commit 96ab15d into main Jul 31, 2025
6 of 8 checks passed
@PriyanshuC132 PriyanshuC132 deleted the txn-latency-routing branch July 31, 2025 14:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants