juspay / juspay/decision-engine

feat(feedback): add Gateway Status Mapping (GSM) table to drive retry decisions from error codes

Open
#236 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
Rust
Stars
128
Forks
36
Avg merge
1d 15h
Merged PRs (30d)
34

Description

## Context

The `update-gateway-score` API currently accepts this payload (see [`src/feedback/types.rs`](src/feedback/types.rs) `UpdateScorePayload`):

```rust
pub struct UpdateScorePayload {
pub merchant_id: String,
pub gateway: String,
pub gateway_reference_id: Option,
pub status: TxnStatus,
pub payment_id: String,
pub enforce_dynamic_routing_failure: Option,
pub txn_latency: Option,
}
```

And the response (`UpdateScoreResponse`) only returns a success message. There is no way for the caller to learn what the engine thinks should happen next after a failure — retry the same gateway, retry with a different gateway, or give up.

In [`juspay/hyperswitch`](https://github.com/juspay/hyperswitch) the equivalent concept is the **Gateway Status Mapping (GSM)** table, which maps `(connector, flow, sub_flow, code, message)` tuples to:

- a unified `status` (e.g. `failure`, `technical_failure`)
- a unified `router_error` classification
- a `decision` (`Retry` same connector, `Requeue` with a different connector, `DoNotRetry`)
- a `step_up_possible` flag
- etc.

Decision engine has no such mapping today, so retry strategy has to be hard-coded on the caller side or inferred from coarse txn status. This causes two concrete problems:

1. Callers can't get a consistent \"what should I do next\" signal from the engine.
2. Gateway scoring (see issue/PR for scoring-on-valid-errors) cannot distinguish an error that indicates a genuinely unhealthy PSP from one that is the merchant's or user's fault.

## Motivation

- Bring feature parity with hyperswitch's GSM so decision engine can own retry policy centrally.
- Let callers drive a unified retry loop via the engine rather than maintaining per-connector retry tables in each application.
- Provide the foundation for the related work \"only score on valid gateway errors\" — that filter needs a canonical classification of each error code.

## Proposal

1. **Add a `gateway_status_mapping` table** (Diesel migration under `migrations_pg/`) keyed on `(gateway, flow, sub_flow, code, message)` with columns for `status`, `router_error`, `decision`, `step_up_possible`, `unified_code`, `unified_message`, etc. Mirror the schema used by hyperswitch's `gateway_status_map` where practical so mappings can be shared.
2. **Extend `UpdateScorePayload`** with optional `error_code`, `error_message`, `flow`, and `sub_flow` fields so callers can tell the engine what actually happened.
3. **Extend `UpdateScoreResponse`** to return a `retry_decision` field (`RETRY_SAME_GATEWAY` | `RETRY_DIFFERENT_GATEWAY` | `DO_NOT_RETRY`) plus optional `unified_code` / `unified_message`.
4. **Implement a GSM lookup helper** in `src/feedback/` (e.g. `gsm.rs`) that queries the table (with a sensible default fallback when no mapping exists) and returns the retry decision. Cache hot mappings in Redis.
5. **Wire the lookup** into `check_and_update_gateway_score_` in `src/feedback/gateway_scoring_service.rs` so the retry decision is computed before returning.
6. **Seed data**: provide a seed migration (or a documented script) with a baseline set of mappings for the gateways currently supported, taken from hyperswitch's public GSM data where licensing allows.
7. **Admin CRUD** (optional, can be a follow-up): endpoints to list / upsert / delete GSM rows so operators can tune retry policy without redeploying.

## Acceptance Criteria

- [ ] New `gateway_status_mapping` table exists with a Diesel migration and Rust schema bindings.
- [ ] `UpdateScorePayload` accepts optional `error_code` / `error_message` / `flow` / `sub_flow`.
- [ ] `UpdateScoreResponse` returns a `retry_decision` derived from the GSM lookup, with a documented default when no mapping matches.
- [ ] Unit tests cover: mapping hit → correct decision; mapping miss → default decision; each of the three decision values is reachable.
- [ ] `docs/api-reference/endpoint/updateGatewayScore.mdx` and `docs/openapi.json` are updated to reflect the new request/response fields.
- [ ] Seed migration installs a baseline GSM dataset so a fresh deploy has non-empty retry policy.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.