Bug: [REFACTOR] add `error_category` column to gsm table



Add `error_category` column to gsm table which is an enum in application. These could be used to take multiple decisions but currently these are required for elimination routing in which these categories will serve as bucket name.
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index e3f54813a43..ae9265aa41d 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -7776,6 +7776,16 @@
           }
         }
       },
+      "ErrorCategory": {
+        "type": "string",
+        "enum": [
+          "frm_decline",
+          "processor_downtime",
+          "processor_decline_unauthorized",
+          "issue_with_payment_method",
+          "processor_decline_incorrect_data"
+        ]
+      },
       "ErrorDetails": {
         "type": "object",
         "description": "Error details for the payment",
@@ -9162,6 +9172,14 @@
             "type": "string",
             "description": "error message unified across the connectors",
             "nullable": true
+          },
+          "error_category": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/ErrorCategory"
+              }
+            ],
+            "nullable": true
           }
         }
       },
@@ -9295,6 +9313,14 @@
             "type": "string",
             "description": "error message unified across the connectors",
             "nullable": true
+          },
+          "error_category": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/ErrorCategory"
+              }
+            ],
+            "nullable": true
           }
         }
       },
@@ -9391,6 +9417,14 @@
             "type": "string",
             "description": "error message unified across the connectors",
             "nullable": true
+          },
+          "error_category": {
+            "allOf": [
+              {
+                "$ref": "#/components/schemas/ErrorCategory"
+              }
+            ],
+            "nullable": true
           }
         }
       },
diff --git a/crates/api_models/src/gsm.rs b/crates/api_models/src/gsm.rs
index 30e49062439..b95794ef707 100644
--- a/crates/api_models/src/gsm.rs
+++ b/crates/api_models/src/gsm.rs
@@ -1,3 +1,4 @@
+use common_enums::ErrorCategory;
 use utoipa::ToSchema;
 
 use crate::enums::Connector;
@@ -26,6 +27,8 @@ pub struct GsmCreateRequest {
     pub unified_code: Option<String>,
     /// error message unified across the connectors
     pub unified_message: Option<String>,
+    /// category in which error belongs to
+    pub error_category: Option<ErrorCategory>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
@@ -88,6 +91,8 @@ pub struct GsmUpdateRequest {
     pub unified_code: Option<String>,
     /// error message unified across the connectors
     pub unified_message: Option<String>,
+    /// category in which error belongs to
+    pub error_category: Option<ErrorCategory>,
 }
 
 #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)]
@@ -141,4 +146,6 @@ pub struct GsmResponse {
     pub unified_code: Option<String>,
     /// error message unified across the connectors
     pub unified_message: Option<String>,
+    /// category in which error belongs to
+    pub error_category: Option<ErrorCategory>,
 }
diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs
index 781b5e3710a..1c7eaf8878b 100644
--- a/crates/common_enums/src/enums.rs
+++ b/crates/common_enums/src/enums.rs
@@ -3357,3 +3357,28 @@ pub enum ConnectorMandateStatus {
     /// Indicates that the connector mandate  is not active and hence cannot be used for payments.
     Inactive,
 }
+
+#[derive(
+    Clone,
+    Copy,
+    Debug,
+    strum::Display,
+    PartialEq,
+    Eq,
+    serde::Serialize,
+    serde::Deserialize,
+    strum::EnumString,
+    ToSchema,
+    PartialOrd,
+    Ord,
+)]
+#[router_derive::diesel_enum(storage_type = "text")]
+#[serde(rename_all = "snake_case")]
+#[strum(serialize_all = "snake_case")]
+pub enum ErrorCategory {
+    FrmDecline,
+    ProcessorDowntime,
+    ProcessorDeclineUnauthorized,
+    IssueWithPaymentMethod,
+    ProcessorDeclineIncorrectData,
+}
diff --git a/crates/diesel_models/src/gsm.rs b/crates/diesel_models/src/gsm.rs
index 6a444410fa8..aba8d75a6eb 100644
--- a/crates/diesel_models/src/gsm.rs
+++ b/crates/diesel_models/src/gsm.rs
@@ -1,5 +1,6 @@
 //! Gateway status mapping
 
+use common_enums::ErrorCategory;
 use common_utils::{
     custom_serde,
     events::{ApiEventMetric, ApiEventsType},
@@ -39,6 +40,7 @@ pub struct GatewayStatusMap {
     pub step_up_possible: bool,
     pub unified_code: Option<String>,
     pub unified_message: Option<String>,
+    pub error_category: Option<ErrorCategory>,
 }
 
 #[derive(Clone, Debug, Eq, PartialEq, Insertable)]
@@ -55,17 +57,11 @@ pub struct GatewayStatusMappingNew {
     pub step_up_possible: bool,
     pub unified_code: Option<String>,
     pub unified_message: Option<String>,
+    pub error_category: Option<ErrorCategory>,
 }
 
 #[derive(
-    Clone,
-    Debug,
-    PartialEq,
-    Eq,
-    AsChangeset,
-    router_derive::DebugAsDisplay,
-    Default,
-    serde::Deserialize,
+    Clone, Debug, PartialEq, Eq, AsChangeset, router_derive::DebugAsDisplay, serde::Deserialize,
 )]
 #[diesel(table_name = gateway_status_map)]
 pub struct GatewayStatusMapperUpdateInternal {
@@ -80,6 +76,8 @@ pub struct GatewayStatusMapperUpdateInternal {
     pub step_up_possible: Option<bool>,
     pub unified_code: Option<String>,
     pub unified_message: Option<String>,
+    pub error_category: Option<ErrorCategory>,
+    pub last_modified: PrimitiveDateTime,
 }
 
 #[derive(Debug)]
@@ -90,6 +88,7 @@ pub struct GatewayStatusMappingUpdate {
     pub step_up_possible: Option<bool>,
     pub unified_code: Option<String>,
     pub unified_message: Option<String>,
+    pub error_category: Option<ErrorCategory>,
 }
 
 impl From<GatewayStatusMappingUpdate> for GatewayStatusMapperUpdateInternal {
@@ -101,6 +100,7 @@ impl From<GatewayStatusMappingUpdate> for GatewayStatusMapperUpdateInternal {
             step_up_possible,
             unified_code,
             unified_message,
+            error_category,
         } = value;
         Self {
             status,
@@ -109,7 +109,13 @@ impl From<GatewayStatusMappingUpdate> for GatewayStatusMapperUpdateInternal {
             step_up_possible,
             unified_code,
             unified_message,
-            ..Default::default()
+            error_category,
+            last_modified: common_utils::date_time::now(),
+            connector: None,
+            flow: None,
+            sub_flow: None,
+            code: None,
+            message: None,
         }
     }
 }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index d3e560fc048..b6f1a4f8d05 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -508,6 +508,8 @@ diesel::table! {
         unified_code -> Nullable<Varchar>,
         #[max_length = 1024]
         unified_message -> Nullable<Varchar>,
+        #[max_length = 64]
+        error_category -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index f6bab9071cd..470868ed82d 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -520,6 +520,8 @@ diesel::table! {
         unified_code -> Nullable<Varchar>,
         #[max_length = 1024]
         unified_message -> Nullable<Varchar>,
+        #[max_length = 64]
+        error_category -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs
index ed6efea27f8..8b5940ebf61 100644
--- a/crates/openapi/src/openapi.rs
+++ b/crates/openapi/src/openapi.rs
@@ -605,6 +605,7 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::blocklist::ToggleBlocklistResponse,
         api_models::blocklist::ListBlocklistQuery,
         api_models::enums::BlocklistDataKind,
+        api_models::enums::ErrorCategory,
         api_models::webhook_events::EventListItemResponse,
         api_models::webhook_events::EventRetrieveResponse,
         api_models::webhook_events::OutgoingWebhookRequestContent,
diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs
index a756d9fb1b1..2221055be5a 100644
--- a/crates/openapi/src/openapi_v2.rs
+++ b/crates/openapi/src/openapi_v2.rs
@@ -569,6 +569,7 @@ Never share your secret api keys. Keep them guarded and secure.
         api_models::blocklist::ToggleBlocklistResponse,
         api_models::blocklist::ListBlocklistQuery,
         api_models::enums::BlocklistDataKind,
+        api_models::enums::ErrorCategory,
         api_models::webhook_events::EventListItemResponse,
         api_models::webhook_events::EventRetrieveResponse,
         api_models::webhook_events::OutgoingWebhookRequestContent,
diff --git a/crates/router/src/core/gsm.rs b/crates/router/src/core/gsm.rs
index 4a678a5c408..c7daee111a9 100644
--- a/crates/router/src/core/gsm.rs
+++ b/crates/router/src/core/gsm.rs
@@ -67,6 +67,7 @@ pub async fn update_gsm_rule(
         step_up_possible,
         unified_code,
         unified_message,
+        error_category,
     } = gsm_request;
     GsmInterface::update_gsm_rule(
         db,
@@ -82,6 +83,7 @@ pub async fn update_gsm_rule(
             step_up_possible,
             unified_code,
             unified_message,
+            error_category,
         },
     )
     .await
diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs
index 4ae02668957..ef21dba9e4b 100644
--- a/crates/router/src/types/transformers.rs
+++ b/crates/router/src/types/transformers.rs
@@ -1686,6 +1686,7 @@ impl ForeignFrom<gsm_api_types::GsmCreateRequest> for storage::GatewayStatusMapp
             step_up_possible: value.step_up_possible,
             unified_code: value.unified_code,
             unified_message: value.unified_message,
+            error_category: value.error_category,
         }
     }
 }
@@ -1704,6 +1705,7 @@ impl ForeignFrom<storage::GatewayStatusMap> for gsm_api_types::GsmResponse {
             step_up_possible: value.step_up_possible,
             unified_code: value.unified_code,
             unified_message: value.unified_message,
+            error_category: value.error_category,
         }
     }
 }
diff --git a/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql
new file mode 100644
index 00000000000..609afbdfd75
--- /dev/null
+++ b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/down.sql
@@ -0,0 +1,2 @@
+-- This file should undo anything in `up.sql`
+ALTER TABLE gateway_status_map DROP COLUMN error_category;
diff --git a/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql
new file mode 100644
index 00000000000..298f6d263aa
--- /dev/null
+++ b/migrations/2024-11-24-104438_add_error_category_col_to_gsm/up.sql
@@ -0,0 +1,2 @@
+-- Your SQL goes here
+ALTER TABLE gateway_status_map ADD COLUMN error_category VARCHAR(64);

## Type of Change
<!-- Put an `x` in the boxes that apply -->

- [ ] Bugfix
- [ ] New feature
- [ ] Enhancement
- [ ] Refactoring
- [ ] Dependency updates
- [ ] Documentation
- [ ] CI/CD

## Description
<!-- Describe your changes in detail -->
This PR adds `error_category` column to gsm table which is an enum in application. These could be used to take multiple decisions in future but currently these are required for elimination routing in which these categories will serve as bucket name.
Additionally the pr fixes the `last_modified` column not being updated in gsm table during `/update`

### Additional Changes

- [ ] This PR modifies the API contract
- [ ] This PR modifies the database schema
- [ ] This PR modifies application configuration/environment variables

<!--
Provide links to the files with corresponding changes.

Following are the paths where you can find config files:
1. `config`
2. `crates/router/src/configs`
3. `loadtest/config`
-->


## Motivation and Context
<!--
Why is this change required? What problem does it solve?
If it fixes an open issue, please link to the issue here.

If you don't have an issue, we'd recommend starting with one first so the PR
can focus on the implementation (unless it is an obvious bug or documentation fix
that will have little conversation).
-->


## How did you test it?
<!--
Did you write an integration/unit/API test to verify the code changes?
Or did you test this change manually (provide relevant screenshots)?
-->
Added new column to GSM table. (`error_category`)

```
curl --location 'http://localhost:8080/gsm' \
--header 'Content-Type: application/json' \
--header 'Accept: application/json' \
--header 'api-key: test_admin' \
--data '{
  "connector": "stripe",
  "flow" : "flow1",
  "sub_flow": "sub_flow",
  "code": "incorrect_cvc",
  "status": "status1",
  "decision": "retry",
  "message": "xyz",
  "step_up_possible": false,
  "error_category": "issue_with_payment_method"
}'
```


## Checklist
<!-- Put an `x` in the boxes that apply -->

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