diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 3a84df8141..e38d2cfa96 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -16000,7 +16000,8 @@ }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible" + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true }, "unified_code": { "type": "string", @@ -16022,7 +16023,24 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible" + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true + }, + "feature": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeature" + } + ], + "nullable": true + }, + "feature_data": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeatureData" + } + ], + "nullable": true } } }, @@ -16030,7 +16048,6 @@ "type": "string", "enum": [ "retry", - "requeue", "do_default" ] }, @@ -16097,6 +16114,28 @@ } } }, + "GsmFeature": { + "type": "string", + "enum": [ + "retry" + ] + }, + "GsmFeatureData": { + "oneOf": [ + { + "type": "object", + "required": [ + "retry" + ], + "properties": { + "retry": { + "$ref": "#/components/schemas/RetryFeatureData" + } + } + } + ], + "description": "Contains the data relevant to the specified GSM feature, if applicable.\nFor example, if the `feature` is `Retry`, this will include configuration\ndetails specific to the retry behavior." + }, "GsmResponse": { "type": "object", "required": [ @@ -16108,7 +16147,9 @@ "status", "decision", "step_up_possible", - "clear_pan_possible" + "clear_pan_possible", + "feature", + "feature_data" ], "properties": { "connector": { @@ -16141,12 +16182,12 @@ "nullable": true }, "decision": { - "type": "string", - "description": "decision to be taken for auto retries flow" + "$ref": "#/components/schemas/GsmDecision" }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible" + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true }, "unified_code": { "type": "string", @@ -16168,7 +16209,14 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible" + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true + }, + "feature": { + "$ref": "#/components/schemas/GsmFeature" + }, + "feature_data": { + "$ref": "#/components/schemas/GsmFeatureData" } } }, @@ -16253,7 +16301,8 @@ }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible", + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true, "nullable": true }, "unified_code": { @@ -16276,7 +16325,24 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible", + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true, + "nullable": true + }, + "feature": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeature" + } + ], + "nullable": true + }, + "feature_data": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeatureData" + } + ], "nullable": true } } @@ -29008,6 +29074,33 @@ "requeue" ] }, + "RetryFeatureData": { + "type": "object", + "description": "Represents the data associated with a retry feature in GSM.", + "required": [ + "step_up_possible", + "clear_pan_possible", + "alternate_network_possible", + "decision" + ], + "properties": { + "step_up_possible": { + "type": "boolean", + "description": "indicates if step_up retry is possible" + }, + "clear_pan_possible": { + "type": "boolean", + "description": "indicates if retry with pan is possible" + }, + "alternate_network_possible": { + "type": "boolean", + "description": "indicates if retry with alternate network possible" + }, + "decision": { + "$ref": "#/components/schemas/GsmDecision" + } + } + }, "RevokeApiKeyResponse": { "type": "object", "description": "The response body for revoking an API Key.", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 3dcaf86d16..8347355c12 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -12132,7 +12132,8 @@ }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible" + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true }, "unified_code": { "type": "string", @@ -12154,7 +12155,24 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible" + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true + }, + "feature": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeature" + } + ], + "nullable": true + }, + "feature_data": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeatureData" + } + ], + "nullable": true } } }, @@ -12162,7 +12180,6 @@ "type": "string", "enum": [ "retry", - "requeue", "do_default" ] }, @@ -12229,6 +12246,28 @@ } } }, + "GsmFeature": { + "type": "string", + "enum": [ + "retry" + ] + }, + "GsmFeatureData": { + "oneOf": [ + { + "type": "object", + "required": [ + "retry" + ], + "properties": { + "retry": { + "$ref": "#/components/schemas/RetryFeatureData" + } + } + } + ], + "description": "Contains the data relevant to the specified GSM feature, if applicable.\nFor example, if the `feature` is `Retry`, this will include configuration\ndetails specific to the retry behavior." + }, "GsmResponse": { "type": "object", "required": [ @@ -12240,7 +12279,9 @@ "status", "decision", "step_up_possible", - "clear_pan_possible" + "clear_pan_possible", + "feature", + "feature_data" ], "properties": { "connector": { @@ -12273,12 +12314,12 @@ "nullable": true }, "decision": { - "type": "string", - "description": "decision to be taken for auto retries flow" + "$ref": "#/components/schemas/GsmDecision" }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible" + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true }, "unified_code": { "type": "string", @@ -12300,7 +12341,14 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible" + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true + }, + "feature": { + "$ref": "#/components/schemas/GsmFeature" + }, + "feature_data": { + "$ref": "#/components/schemas/GsmFeatureData" } } }, @@ -12385,7 +12433,8 @@ }, "step_up_possible": { "type": "boolean", - "description": "indicates if step_up retry is possible", + "description": "indicates if step_up retry is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true, "nullable": true }, "unified_code": { @@ -12408,7 +12457,24 @@ }, "clear_pan_possible": { "type": "boolean", - "description": "indicates if retry with pan is possible", + "description": "indicates if retry with pan is possible\n**Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant.", + "deprecated": true, + "nullable": true + }, + "feature": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeature" + } + ], + "nullable": true + }, + "feature_data": { + "allOf": [ + { + "$ref": "#/components/schemas/GsmFeatureData" + } + ], "nullable": true } } @@ -22796,6 +22862,33 @@ "requeue" ] }, + "RetryFeatureData": { + "type": "object", + "description": "Represents the data associated with a retry feature in GSM.", + "required": [ + "step_up_possible", + "clear_pan_possible", + "alternate_network_possible", + "decision" + ], + "properties": { + "step_up_possible": { + "type": "boolean", + "description": "indicates if step_up retry is possible" + }, + "clear_pan_possible": { + "type": "boolean", + "description": "indicates if retry with pan is possible" + }, + "alternate_network_possible": { + "type": "boolean", + "description": "indicates if retry with alternate network possible" + }, + "decision": { + "$ref": "#/components/schemas/GsmDecision" + } + } + }, "RevenueRecoveryMetadata": { "type": "object", "description": "Revenue recovery metadata for merchant connector account", diff --git a/crates/api_models/src/gsm.rs b/crates/api_models/src/gsm.rs index 7ffc9c6941..1b2800d11e 100644 --- a/crates/api_models/src/gsm.rs +++ b/crates/api_models/src/gsm.rs @@ -1,12 +1,12 @@ -use common_enums::ErrorCategory; use utoipa::ToSchema; -use crate::enums::Connector; +use crate::enums as api_enums; #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct GsmCreateRequest { /// The connector through which payment has gone through - pub connector: Connector, + #[schema(value_type = Connector)] + pub connector: api_enums::Connector, /// The flow in which the code and message occurred for a connector pub flow: String, /// The sub_flow in which the code and message occurred for a connector @@ -20,23 +20,41 @@ pub struct GsmCreateRequest { /// optional error provided by the router pub router_error: Option, /// decision to be taken for auto retries flow - pub decision: GsmDecision, + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] + #[schema(value_type = GsmDecision)] + pub decision: api_enums::GsmDecision, /// indicates if step_up retry is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub step_up_possible: bool, /// error code unified across the connectors pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, /// category in which error belongs to - pub error_category: Option, + #[schema(value_type = Option)] + pub error_category: Option, /// indicates if retry with pan is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub clear_pan_possible: bool, + /// Indicates the GSM feature associated with the request, + /// such as retry mechanisms or other specific functionalities provided by the system. + #[schema(value_type = Option)] + pub feature: Option, + /// Contains the data relevant to the specified GSM feature, if applicable. + /// For example, if the `feature` is `Retry`, this will include configuration + /// details specific to the retry behavior. + #[schema(value_type = Option)] + pub feature_data: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct GsmRetrieveRequest { /// The connector through which payment has gone through - pub connector: Connector, + #[schema(value_type = Connector)] + pub connector: api_enums::Connector, /// The flow in which the code and message occurred for a connector pub flow: String, /// The sub_flow in which the code and message occurred for a connector @@ -47,28 +65,6 @@ pub struct GsmRetrieveRequest { pub message: String, } -#[derive( - Default, - Clone, - Copy, - Debug, - strum::Display, - PartialEq, - Eq, - serde::Serialize, - serde::Deserialize, - strum::EnumString, - ToSchema, -)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -pub enum GsmDecision { - Retry, - Requeue, - #[default] - DoDefault, -} - #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct GsmUpdateRequest { /// The connector through which payment has gone through @@ -86,17 +82,34 @@ pub struct GsmUpdateRequest { /// optional error provided by the router pub router_error: Option, /// decision to be taken for auto retries flow - pub decision: Option, + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] + #[schema(value_type = Option)] + pub decision: Option, /// indicates if step_up retry is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub step_up_possible: Option, /// error code unified across the connectors pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, /// category in which error belongs to - pub error_category: Option, + #[schema(value_type = Option)] + pub error_category: Option, /// indicates if retry with pan is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub clear_pan_possible: Option, + /// Indicates the GSM feature associated with the request, + /// such as retry mechanisms or other specific functionalities provided by the system. + #[schema(value_type = Option)] + pub feature: Option, + /// Contains the data relevant to the specified GSM feature, if applicable. + /// For example, if the `feature` is `Retry`, this will include configuration + /// details specific to the retry behavior. + #[schema(value_type = Option)] + pub feature_data: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize, ToSchema)] @@ -143,15 +156,32 @@ pub struct GsmResponse { /// optional error provided by the router pub router_error: Option, /// decision to be taken for auto retries flow - pub decision: String, + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] + #[schema(value_type = GsmDecision)] + pub decision: api_enums::GsmDecision, /// indicates if step_up retry is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub step_up_possible: bool, /// error code unified across the connectors pub unified_code: Option, /// error message unified across the connectors pub unified_message: Option, /// category in which error belongs to - pub error_category: Option, + #[schema(value_type = Option)] + pub error_category: Option, /// indicates if retry with pan is possible + /// **Deprecated**: This field is now included as part of `feature_data` under the `Retry` variant. + #[schema(deprecated)] pub clear_pan_possible: bool, + /// Indicates the GSM feature associated with the request, + /// such as retry mechanisms or other specific functionalities provided by the system. + #[schema(value_type = GsmFeature)] + pub feature: api_enums::GsmFeature, + /// Contains the data relevant to the specified GSM feature, if applicable. + /// For example, if the `feature` is `Retry`, this will include configuration + /// details specific to the retry behavior. + #[schema(value_type = GsmFeatureData)] + pub feature_data: Option, } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 4daa373c01..6b35bbaf9c 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -267,6 +267,46 @@ pub enum RevenueRecoveryAlgorithmType { Cascading, } +#[derive( + Default, + Clone, + Copy, + Debug, + strum::Display, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum GsmDecision { + Retry, + #[default] + DoDefault, +} + +#[derive( + Clone, + Copy, + Debug, + strum::Display, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + strum::EnumString, + ToSchema, +)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +#[router_derive::diesel_enum(storage_type = "text")] +pub enum GsmFeature { + Retry, +} + /// Specifies the type of cardholder authentication to be applied for a payment. /// /// - `ThreeDs`: Requests 3D Secure (3DS) authentication. If the card is enrolled, 3DS authentication will be activated, potentially shifting chargeback liability to the issuer. diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs index e800cb9cb6..3836aaabe7 100644 --- a/crates/common_types/src/domain.rs +++ b/crates/common_types/src/domain.rs @@ -113,3 +113,71 @@ pub struct ConnectorResponseData { /// Stringified connector raw response body pub raw_connector_response: Option>, } + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, AsExpression, ToSchema)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +/// Contains the data relevant to the specified GSM feature, if applicable. +/// For example, if the `feature` is `Retry`, this will include configuration +/// details specific to the retry behavior. +pub enum GsmFeatureData { + /// Represents the data associated with a retry feature in GSM. + Retry(RetryFeatureData), +} + +/// Represents the data associated with a retry feature in GSM. +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq, AsExpression, ToSchema)] +#[diesel(sql_type = Jsonb)] +pub struct RetryFeatureData { + /// indicates if step_up retry is possible + pub step_up_possible: bool, + /// indicates if retry with pan is possible + pub clear_pan_possible: bool, + /// indicates if retry with alternate network possible + pub alternate_network_possible: bool, + /// decision to be taken for auto retries flow + #[schema(value_type = GsmDecision)] + pub decision: common_enums::GsmDecision, +} + +impl_to_sql_from_sql_json!(GsmFeatureData); +impl_to_sql_from_sql_json!(RetryFeatureData); + +impl GsmFeatureData { + /// Retrieves the retry feature data if it exists. + pub fn get_retry_feature_data(&self) -> Option { + match self { + Self::Retry(data) => Some(data.clone()), + } + } + + /// Retrieves the decision from the retry feature data. + pub fn get_decision(&self) -> common_enums::GsmDecision { + match self { + Self::Retry(data) => data.decision, + } + } +} + +/// Implementation of methods for `RetryFeatureData` +impl RetryFeatureData { + /// Checks if step-up retry is possible. + pub fn is_step_up_possible(&self) -> bool { + self.step_up_possible + } + + /// Checks if retry with PAN is possible. + pub fn is_clear_pan_possible(&self) -> bool { + self.clear_pan_possible + } + + /// Checks if retry with alternate network is possible. + pub fn is_alternate_network_possible(&self) -> bool { + self.alternate_network_possible + } + + /// Retrieves the decision to be taken for auto retries flow. + pub fn get_decision(&self) -> common_enums::GsmDecision { + self.decision + } +} diff --git a/crates/diesel_models/src/gsm.rs b/crates/diesel_models/src/gsm.rs index 46583a5b22..b75b0e6ce8 100644 --- a/crates/diesel_models/src/gsm.rs +++ b/crates/diesel_models/src/gsm.rs @@ -1,5 +1,4 @@ //! Gateway status mapping - use common_enums::ErrorCategory; use common_utils::{ custom_serde, @@ -15,8 +14,6 @@ use crate::schema::gateway_status_map; Debug, Eq, PartialEq, - Ord, - PartialOrd, router_derive::DebugAsDisplay, Identifiable, Queryable, @@ -42,6 +39,8 @@ pub struct GatewayStatusMap { pub unified_message: Option, pub error_category: Option, pub clear_pan_possible: bool, + pub feature_data: Option, + pub feature: Option, } #[derive(Clone, Debug, Eq, PartialEq, Insertable)] @@ -60,6 +59,8 @@ pub struct GatewayStatusMappingNew { pub unified_message: Option, pub error_category: Option, pub clear_pan_possible: bool, + pub feature_data: Option, + pub feature: Option, } #[derive( @@ -81,6 +82,8 @@ pub struct GatewayStatusMapperUpdateInternal { pub error_category: Option, pub last_modified: PrimitiveDateTime, pub clear_pan_possible: Option, + pub feature_data: Option, + pub feature: Option, } #[derive(Debug)] @@ -93,6 +96,8 @@ pub struct GatewayStatusMappingUpdate { pub unified_message: Option, pub error_category: Option, pub clear_pan_possible: Option, + pub feature_data: Option, + pub feature: Option, } impl From for GatewayStatusMapperUpdateInternal { @@ -106,6 +111,8 @@ impl From for GatewayStatusMapperUpdateInternal { unified_message, error_category, clear_pan_possible, + feature_data, + feature, } = value; Self { status, @@ -122,6 +129,8 @@ impl From for GatewayStatusMapperUpdateInternal { code: None, message: None, clear_pan_possible, + feature_data, + feature, } } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 6788f47827..c141f76553 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -595,6 +595,9 @@ diesel::table! { #[max_length = 64] error_category -> Nullable, clear_pan_possible -> Bool, + feature_data -> Nullable, + #[max_length = 64] + feature -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index c5579b6c67..e434e43b2c 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -607,6 +607,9 @@ diesel::table! { #[max_length = 64] error_category -> Nullable, clear_pan_possible -> Bool, + feature_data -> Nullable, + #[max_length = 64] + feature -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/gsm.rs b/crates/hyperswitch_domain_models/src/gsm.rs new file mode 100644 index 0000000000..59319c89e5 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/gsm.rs @@ -0,0 +1,129 @@ +use common_utils::{errors::ValidationError, ext_traits::StringExt}; +use serde::{self, Deserialize, Serialize}; + +#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)] +pub struct GatewayStatusMap { + pub connector: String, + pub flow: String, + pub sub_flow: String, + pub code: String, + pub message: String, + pub status: String, + pub router_error: Option, + pub unified_code: Option, + pub unified_message: Option, + pub error_category: Option, + pub feature_data: common_types::domain::GsmFeatureData, + pub feature: common_enums::GsmFeature, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct GatewayStatusMappingUpdate { + pub status: Option, + pub router_error: Option>, + pub decision: Option, + pub step_up_possible: Option, + pub unified_code: Option, + pub unified_message: Option, + pub error_category: Option, + pub clear_pan_possible: Option, + pub feature_data: Option, + pub feature: Option, +} + +impl TryFrom for diesel_models::gsm::GatewayStatusMappingNew { + type Error = error_stack::Report; + + fn try_from(value: GatewayStatusMap) -> Result { + Ok(Self { + connector: value.connector.to_string(), + flow: value.flow, + sub_flow: value.sub_flow, + code: value.code, + message: value.message, + status: value.status.clone(), + router_error: value.router_error, + decision: value.feature_data.get_decision().to_string(), + step_up_possible: value + .feature_data + .get_retry_feature_data() + .map(|retry_feature_data| retry_feature_data.is_step_up_possible()) + .unwrap_or(false), + unified_code: value.unified_code, + unified_message: value.unified_message, + error_category: value.error_category, + clear_pan_possible: value + .feature_data + .get_retry_feature_data() + .map(|retry_feature_data| retry_feature_data.is_clear_pan_possible()) + .unwrap_or(false), + feature_data: Some(value.feature_data), + feature: Some(value.feature), + }) + } +} + +impl TryFrom for diesel_models::gsm::GatewayStatusMappingUpdate { + type Error = error_stack::Report; + + fn try_from(value: GatewayStatusMappingUpdate) -> Result { + Ok(Self { + status: value.status, + router_error: value.router_error, + decision: value.decision.map(|gsm_decision| gsm_decision.to_string()), + step_up_possible: value.step_up_possible, + unified_code: value.unified_code, + unified_message: value.unified_message, + error_category: value.error_category, + clear_pan_possible: value.clear_pan_possible, + feature_data: value.feature_data, + feature: value.feature, + }) + } +} + +impl TryFrom for GatewayStatusMap { + type Error = ValidationError; + + fn try_from(item: diesel_models::gsm::GatewayStatusMap) -> Result { + let decision = + StringExt::::parse_enum(item.decision, "GsmDecision") + .map_err(|_| ValidationError::InvalidValue { + message: "Failed to parse GsmDecision".to_string(), + })?; + let db_feature_data = item.feature_data; + + // The only case where `FeatureData` can be null is for legacy records + // (i.e., records created before `FeatureData` and related features were introduced). + // At that time, the only supported feature was `Retry`, so it's safe to default to it. + let feature_data = match db_feature_data { + Some(common_types::domain::GsmFeatureData::Retry(data)) => { + common_types::domain::GsmFeatureData::Retry(data) + } + None => common_types::domain::GsmFeatureData::Retry( + common_types::domain::RetryFeatureData { + step_up_possible: item.step_up_possible, + clear_pan_possible: item.clear_pan_possible, + alternate_network_possible: false, + decision, + }, + ), + }; + + let feature = item.feature.unwrap_or(common_enums::GsmFeature::Retry); + Ok(Self { + connector: item.connector, + flow: item.flow, + sub_flow: item.sub_flow, + code: item.code, + message: item.message, + status: item.status, + router_error: item.router_error, + unified_code: item.unified_code, + unified_message: item.unified_message, + error_category: item.error_category, + feature_data, + feature, + }) + } +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index c7df1e3749..30d0913a71 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -14,6 +14,7 @@ pub mod customer; pub mod disputes; pub mod errors; pub mod ext_traits; +pub mod gsm; pub mod mandates; pub mod merchant_account; pub mod merchant_connector_account; diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index efa92af978..bb2937a653 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -400,7 +400,10 @@ Never share your secret api keys. Keep them guarded and secure. api_models::gsm::GsmDeleteRequest, api_models::gsm::GsmDeleteResponse, api_models::gsm::GsmResponse, - api_models::gsm::GsmDecision, + api_models::enums::GsmDecision, + api_models::enums::GsmFeature, + common_types::domain::GsmFeatureData, + common_types::domain::RetryFeatureData, api_models::payments::AddressDetails, api_models::payments::BankDebitData, api_models::payments::AliPayQr, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 81e8f10dcd..23f0fa2d0d 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -352,7 +352,10 @@ Never share your secret api keys. Keep them guarded and secure. api_models::gsm::GsmDeleteRequest, api_models::gsm::GsmDeleteResponse, api_models::gsm::GsmResponse, - api_models::gsm::GsmDecision, + api_models::enums::GsmDecision, + api_models::enums::GsmFeature, + common_types::domain::GsmFeatureData, + common_types::domain::RetryFeatureData, api_models::payments::NullObject, api_models::payments::AddressDetails, api_models::payments::BankDebitData, diff --git a/crates/router/src/core/gsm.rs b/crates/router/src/core/gsm.rs index 6c544791a1..4bd93a4e0e 100644 --- a/crates/router/src/core/gsm.rs +++ b/crates/router/src/core/gsm.rs @@ -1,16 +1,12 @@ use api_models::gsm as gsm_api_types; -use diesel_models::gsm as storage; use error_stack::ResultExt; use router_env::{instrument, tracing}; use crate::{ - core::{ - errors, - errors::{RouterResponse, StorageErrorExt}, - }, + core::errors::{self, RouterResponse, StorageErrorExt}, db::gsm::GsmInterface, services, - types::transformers::ForeignInto, + types::transformers::{ForeignFrom, ForeignInto}, SessionState, }; @@ -55,6 +51,24 @@ pub async fn update_gsm_rule( gsm_request: gsm_api_types::GsmUpdateRequest, ) -> RouterResponse { let db = state.store.as_ref(); + let connector = gsm_request.connector.clone(); + let flow = gsm_request.flow.clone(); + let code = gsm_request.code.clone(); + let sub_flow = gsm_request.sub_flow.clone(); + let message = gsm_request.message.clone(); + + let gsm_db_record = + GsmInterface::find_gsm_rule(db, connector.to_string(), flow, sub_flow, code, message) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "GSM with given key does not exist in our records".to_string(), + })?; + + let inferred_feature_info = <( + common_enums::GsmFeature, + common_types::domain::GsmFeatureData, + )>::foreign_from((&gsm_request, gsm_db_record)); + let gsm_api_types::GsmUpdateRequest { connector, flow, @@ -69,6 +83,8 @@ pub async fn update_gsm_rule( unified_message, error_category, clear_pan_possible, + feature, + feature_data, } = gsm_request; GsmInterface::update_gsm_rule( db, @@ -77,15 +93,25 @@ pub async fn update_gsm_rule( sub_flow, code, message, - storage::GatewayStatusMappingUpdate { - decision: decision.map(|d| d.to_string()), + hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate { + decision, status, router_error: Some(router_error), - step_up_possible, + step_up_possible: feature_data + .as_ref() + .and_then(|feature_data| feature_data.get_retry_feature_data()) + .map(|retry_feature_data| retry_feature_data.is_step_up_possible()) + .or(step_up_possible), unified_code, unified_message, error_category, - clear_pan_possible, + clear_pan_possible: feature_data + .as_ref() + .and_then(|feature_data| feature_data.get_retry_feature_data()) + .map(|retry_feature_data| retry_feature_data.is_clear_pan_possible()) + .or(clear_pan_possible), + feature_data: feature_data.or(Some(inferred_feature_info.1)), + feature: feature.or(Some(inferred_feature_info.0)), }, ) .await diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 458fe4bc5b..ca8a970865 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6441,7 +6441,7 @@ pub async fn get_gsm_record( error_message: Option, connector_name: String, flow: String, -) -> Option { +) -> Option { let get_gsm = || async { state.store.find_gsm_rule( connector_name.clone(), diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index aafe25aea6..b435bd191e 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -1,8 +1,8 @@ -use std::{str::FromStr, vec::IntoIter}; +use std::vec::IntoIter; use common_utils::{ext_traits::Encode, types::MinorUnit}; use diesel_models::enums as storage_enums; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use router_env::{ logger, tracing::{self, instrument}, @@ -65,10 +65,10 @@ where let mut initial_gsm = get_gsm(state, &router_data).await?; - //Check if step-up to threeDS is possible and merchant has enabled let step_up_possible = initial_gsm - .clone() - .map(|gsm| gsm.step_up_possible) + .as_ref() + .and_then(|data| data.feature_data.get_retry_feature_data()) + .map(|data| data.is_step_up_possible()) .unwrap_or(false); #[cfg(feature = "v1")] @@ -124,7 +124,7 @@ where }; match get_gsm_decision(gsm) { - api_models::gsm::GsmDecision::Retry => { + storage_enums::GsmDecision::Retry => { retries = get_retries( state, retries, @@ -150,11 +150,13 @@ where .map(|pmd| pmd.is_network_token_payment_method_data()) .unwrap_or(false); + let clear_pan_possible = initial_gsm + .and_then(|data| data.feature_data.get_retry_feature_data()) + .map(|data| data.is_clear_pan_possible()) + .unwrap_or(false); + let should_retry_with_pan = is_network_token - && initial_gsm - .as_ref() - .map(|gsm| gsm.clear_pan_possible) - .unwrap_or(false) + && clear_pan_possible && business_profile.is_clear_pan_retries_enabled; let (connector, routing_decision) = if should_retry_with_pan { @@ -195,14 +197,7 @@ where retries = retries.map(|i| i - 1); } - api_models::gsm::GsmDecision::Requeue => { - Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::NotImplementedMessage::Reason( - "Requeue not implemented".to_string(), - ), - }))? - } - api_models::gsm::GsmDecision::DoDefault => break, + storage_enums::GsmDecision::DoDefault => break, } initial_gsm = None; } @@ -278,7 +273,7 @@ pub async fn get_retries( pub async fn get_gsm( state: &app::SessionState, router_data: &types::RouterData, -) -> RouterResult> { +) -> RouterResult> { let error_response = router_data.response.as_ref().err(); let error_code = error_response.map(|err| err.code.to_owned()); let error_message = error_response.map(|err| err.message.to_owned()); @@ -292,19 +287,11 @@ pub async fn get_gsm( #[instrument(skip_all)] pub fn get_gsm_decision( - option_gsm: Option, -) -> api_models::gsm::GsmDecision { + option_gsm: Option, +) -> storage_enums::GsmDecision { let option_gsm_decision = option_gsm - .and_then(|gsm| { - api_models::gsm::GsmDecision::from_str(gsm.decision.as_str()) - .map_err(|err| { - let api_error = report!(err).change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("gsm decision parsing failed"); - logger::warn!(get_gsm_decision_parse_error=?api_error, "error fetching gsm decision"); - api_error - }) - .ok() - }); + .as_ref() + .map(|gsm| gsm.feature_data.get_decision()); if option_gsm_decision.is_some() { metrics::AUTO_RETRY_GSM_MATCH_COUNT.add(1, &[]); diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index f1f8dad3a0..8a7f87fa60 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -1060,7 +1060,7 @@ pub async fn get_gsm_record( error_message: Option, connector_name: Option, flow: &str, -) -> Option { +) -> Option { let connector_name = connector_name.unwrap_or_default(); let get_gsm = || async { state.store.find_gsm_rule( diff --git a/crates/router/src/core/payouts/retry.rs b/crates/router/src/core/payouts/retry.rs index 30100a580d..e457961360 100644 --- a/crates/router/src/core/payouts/retry.rs +++ b/crates/router/src/core/payouts/retry.rs @@ -1,7 +1,7 @@ -use std::{cmp::Ordering, str::FromStr, vec::IntoIter}; +use std::vec::IntoIter; use common_enums::PayoutRetryType; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use router_env::{ logger, tracing::{self, instrument}, @@ -38,7 +38,7 @@ pub async fn do_gsm_multiple_connector_actions( let gsm = get_gsm(state, &connector, payout_data).await?; match get_gsm_decision(gsm) { - api_models::gsm::GsmDecision::Retry => { + common_enums::GsmDecision::Retry => { retries = get_retries( state, retries, @@ -71,14 +71,7 @@ pub async fn do_gsm_multiple_connector_actions( retries = retries.map(|i| i - 1); } - api_models::gsm::GsmDecision::Requeue => { - Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::NotImplementedMessage::Reason( - "Requeue not implemented".to_string(), - ), - }))? - } - api_models::gsm::GsmDecision::DoDefault => break, + common_enums::GsmDecision::DoDefault => break, } } Ok(()) @@ -102,13 +95,13 @@ pub async fn do_gsm_single_connector_actions( let gsm = get_gsm(state, &original_connector_data, payout_data).await?; // if the error config is same as previous, we break out of the loop - if let Ordering::Equal = gsm.cmp(&previous_gsm) { + if gsm == previous_gsm { break; } previous_gsm.clone_from(&gsm); match get_gsm_decision(gsm) { - api_models::gsm::GsmDecision::Retry => { + common_enums::GsmDecision::Retry => { retries = get_retries( state, retries, @@ -133,14 +126,7 @@ pub async fn do_gsm_single_connector_actions( retries = retries.map(|i| i - 1); } - api_models::gsm::GsmDecision::Requeue => { - Err(report!(errors::ApiErrorResponse::NotImplemented { - message: errors::NotImplementedMessage::Reason( - "Requeue not implemented".to_string(), - ), - }))? - } - api_models::gsm::GsmDecision::DoDefault => break, + common_enums::GsmDecision::DoDefault => break, } } Ok(()) @@ -182,7 +168,7 @@ pub async fn get_gsm( state: &app::SessionState, original_connector_data: &api::ConnectorData, payout_data: &PayoutData, -) -> RouterResult> { +) -> RouterResult> { let error_code = payout_data.payout_attempt.error_code.to_owned(); let error_message = payout_data.payout_attempt.error_message.to_owned(); let connector_name = Some(original_connector_data.connector_name.to_string()); @@ -199,19 +185,9 @@ pub async fn get_gsm( #[instrument(skip_all)] pub fn get_gsm_decision( - option_gsm: Option, -) -> api_models::gsm::GsmDecision { - let option_gsm_decision = option_gsm - .and_then(|gsm| { - api_models::gsm::GsmDecision::from_str(gsm.decision.as_str()) - .map_err(|err| { - let api_error = report!(err).change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("gsm decision parsing failed"); - logger::warn!(get_gsm_decision_parse_error=?api_error, "error fetching gsm decision"); - api_error - }) - .ok() - }); + option_gsm: Option, +) -> common_enums::GsmDecision { + let option_gsm_decision = option_gsm.map(|gsm| gsm.feature_data.get_decision()); if option_gsm_decision.is_some() { metrics::AUTO_PAYOUT_RETRY_GSM_MATCH_COUNT.add(1, &[]); diff --git a/crates/router/src/db/gsm.rs b/crates/router/src/db/gsm.rs index 1aa96e14c1..eb88eecd4c 100644 --- a/crates/router/src/db/gsm.rs +++ b/crates/router/src/db/gsm.rs @@ -1,5 +1,5 @@ use diesel_models::gsm as storage; -use error_stack::report; +use error_stack::{report, ResultExt}; use router_env::{instrument, tracing}; use super::MockDb; @@ -13,8 +13,8 @@ use crate::{ pub trait GsmInterface { async fn add_gsm_rule( &self, - rule: storage::GatewayStatusMappingNew, - ) -> CustomResult; + rule: hyperswitch_domain_models::gsm::GatewayStatusMap, + ) -> CustomResult; async fn find_gsm_decision( &self, connector: String, @@ -30,7 +30,7 @@ pub trait GsmInterface { sub_flow: String, code: String, message: String, - ) -> CustomResult; + ) -> CustomResult; async fn update_gsm_rule( &self, connector: String, @@ -38,8 +38,8 @@ pub trait GsmInterface { sub_flow: String, code: String, message: String, - data: storage::GatewayStatusMappingUpdate, - ) -> CustomResult; + data: hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate, + ) -> CustomResult; async fn delete_gsm_rule( &self, @@ -56,12 +56,19 @@ impl GsmInterface for Store { #[instrument(skip_all)] async fn add_gsm_rule( &self, - rule: storage::GatewayStatusMappingNew, - ) -> CustomResult { + rule: hyperswitch_domain_models::gsm::GatewayStatusMap, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - rule.insert(&conn) + let gsm_db_record = diesel_models::gsm::GatewayStatusMappingNew::try_from(rule) + .change_context(errors::StorageError::SerializationFailed) + .attach_printable("Failed to convert gsm domain models to diesel models")? + .insert(&conn) .await - .map_err(|error| report!(errors::StorageError::from(error))) + .map_err(|error| report!(errors::StorageError::from(error)))?; + + hyperswitch_domain_models::gsm::GatewayStatusMap::try_from(gsm_db_record) + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("Failed to convert gsm diesel models to domain models") } #[instrument(skip_all)] @@ -89,11 +96,16 @@ impl GsmInterface for Store { sub_flow: String, code: String, message: String, - ) -> CustomResult { + ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::GatewayStatusMap::find(&conn, connector, flow, sub_flow, code, message) - .await - .map_err(|error| report!(errors::StorageError::from(error))) + let gsm_db_record = + storage::GatewayStatusMap::find(&conn, connector, flow, sub_flow, code, message) + .await + .map_err(|error| report!(errors::StorageError::from(error)))?; + + hyperswitch_domain_models::gsm::GatewayStatusMap::try_from(gsm_db_record) + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("Failed to convert gsm diesel models to domain models") } #[instrument(skip_all)] @@ -104,12 +116,26 @@ impl GsmInterface for Store { sub_flow: String, code: String, message: String, - data: storage::GatewayStatusMappingUpdate, - ) -> CustomResult { + data: hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate, + ) -> CustomResult { let conn = connection::pg_connection_write(self).await?; - storage::GatewayStatusMap::update(&conn, connector, flow, sub_flow, code, message, data) - .await - .map_err(|error| report!(errors::StorageError::from(error))) + let gsm_update_data = diesel_models::gsm::GatewayStatusMappingUpdate::try_from(data) + .change_context(errors::StorageError::SerializationFailed)?; + let gsm_db_record = storage::GatewayStatusMap::update( + &conn, + connector, + flow, + sub_flow, + code, + message, + gsm_update_data, + ) + .await + .map_err(|error| report!(errors::StorageError::from(error)))?; + + hyperswitch_domain_models::gsm::GatewayStatusMap::try_from(gsm_db_record) + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("Failed to convert gsm diesel models to domain models") } #[instrument(skip_all)] @@ -132,8 +158,8 @@ impl GsmInterface for Store { impl GsmInterface for MockDb { async fn add_gsm_rule( &self, - _rule: storage::GatewayStatusMappingNew, - ) -> CustomResult { + _rule: hyperswitch_domain_models::gsm::GatewayStatusMap, + ) -> CustomResult { Err(errors::StorageError::MockDbError)? } @@ -155,7 +181,7 @@ impl GsmInterface for MockDb { _sub_flow: String, _code: String, _message: String, - ) -> CustomResult { + ) -> CustomResult { Err(errors::StorageError::MockDbError)? } @@ -166,8 +192,8 @@ impl GsmInterface for MockDb { _sub_flow: String, _code: String, _message: String, - _data: storage::GatewayStatusMappingUpdate, - ) -> CustomResult { + _data: hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate, + ) -> CustomResult { Err(errors::StorageError::MockDbError)? } diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index b81e5c1f71..412cc0a4a1 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3185,8 +3185,8 @@ impl RoutingAlgorithmInterface for KafkaStore { impl GsmInterface for KafkaStore { async fn add_gsm_rule( &self, - rule: storage::GatewayStatusMappingNew, - ) -> CustomResult { + rule: hyperswitch_domain_models::gsm::GatewayStatusMap, + ) -> CustomResult { self.diesel_store.add_gsm_rule(rule).await } @@ -3210,7 +3210,7 @@ impl GsmInterface for KafkaStore { sub_flow: String, code: String, message: String, - ) -> CustomResult { + ) -> CustomResult { self.diesel_store .find_gsm_rule(connector, flow, sub_flow, code, message) .await @@ -3223,8 +3223,8 @@ impl GsmInterface for KafkaStore { sub_flow: String, code: String, message: String, - data: storage::GatewayStatusMappingUpdate, - ) -> CustomResult { + data: hyperswitch_domain_models::gsm::GatewayStatusMappingUpdate, + ) -> CustomResult { self.diesel_store .update_gsm_rule(connector, flow, sub_flow, code, message, data) .await diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 7b38342a74..90f2a42359 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1586,42 +1586,129 @@ impl ForeignFrom } } -impl ForeignFrom for storage::GatewayStatusMappingNew { +impl ForeignFrom + for hyperswitch_domain_models::gsm::GatewayStatusMap +{ fn foreign_from(value: gsm_api_types::GsmCreateRequest) -> Self { + let inferred_feature_data = common_types::domain::GsmFeatureData::foreign_from(&value); Self { connector: value.connector.to_string(), flow: value.flow, sub_flow: value.sub_flow, code: value.code, message: value.message, - decision: value.decision.to_string(), status: value.status, router_error: value.router_error, - step_up_possible: value.step_up_possible, unified_code: value.unified_code, unified_message: value.unified_message, error_category: value.error_category, - clear_pan_possible: value.clear_pan_possible, + feature_data: value.feature_data.unwrap_or(inferred_feature_data), + feature: value.feature.unwrap_or(api_enums::GsmFeature::Retry), } } } -impl ForeignFrom for gsm_api_types::GsmResponse { - fn foreign_from(value: storage::GatewayStatusMap) -> Self { +impl ForeignFrom<&gsm_api_types::GsmCreateRequest> for common_types::domain::GsmFeatureData { + fn foreign_from(value: &gsm_api_types::GsmCreateRequest) -> Self { + // Defaulting alternate_network_possible to false as it is provided only in the Retry feature + // If the retry feature is not used, we assume alternate network as false + let alternate_network_possible = false; + + match value.feature { + Some(api_enums::GsmFeature::Retry) | None => { + Self::Retry(common_types::domain::RetryFeatureData { + step_up_possible: value.step_up_possible, + clear_pan_possible: value.clear_pan_possible, + alternate_network_possible, + decision: value.decision, + }) + } + } + } +} + +impl + ForeignFrom<( + &gsm_api_types::GsmUpdateRequest, + hyperswitch_domain_models::gsm::GatewayStatusMap, + )> for (api_enums::GsmFeature, common_types::domain::GsmFeatureData) +{ + fn foreign_from( + (gsm_update_request, gsm_db_record): ( + &gsm_api_types::GsmUpdateRequest, + hyperswitch_domain_models::gsm::GatewayStatusMap, + ), + ) -> Self { + let gsm_db_record_infered_feature = match gsm_db_record.feature_data.get_decision() { + api_enums::GsmDecision::Retry | api_enums::GsmDecision::DoDefault => { + api_enums::GsmFeature::Retry + } + }; + + let gsm_feature = gsm_update_request + .feature + .unwrap_or(gsm_db_record_infered_feature); + + match gsm_feature { + api_enums::GsmFeature::Retry => { + let gsm_db_record_retry_feature_data = + gsm_db_record.feature_data.get_retry_feature_data(); + + let retry_feature_data = common_types::domain::GsmFeatureData::Retry( + common_types::domain::RetryFeatureData { + step_up_possible: gsm_update_request + .step_up_possible + .or(gsm_db_record_retry_feature_data + .clone() + .map(|data| data.step_up_possible)) + .unwrap_or_default(), + clear_pan_possible: gsm_update_request + .clear_pan_possible + .or(gsm_db_record_retry_feature_data + .clone() + .map(|data| data.clear_pan_possible)) + .unwrap_or_default(), + alternate_network_possible: gsm_db_record_retry_feature_data + .map(|data| data.alternate_network_possible) + .unwrap_or_default(), + decision: gsm_update_request + .decision + .or(Some(gsm_db_record.feature_data.get_decision())) + .unwrap_or_default(), + }, + ); + (api_enums::GsmFeature::Retry, retry_feature_data) + } + } + } +} + +impl ForeignFrom for gsm_api_types::GsmResponse { + fn foreign_from(value: hyperswitch_domain_models::gsm::GatewayStatusMap) -> Self { Self { connector: value.connector.to_string(), flow: value.flow, sub_flow: value.sub_flow, code: value.code, message: value.message, - decision: value.decision.to_string(), + decision: value.feature_data.get_decision(), status: value.status, router_error: value.router_error, - step_up_possible: value.step_up_possible, + step_up_possible: value + .feature_data + .get_retry_feature_data() + .map(|data| data.is_step_up_possible()) + .unwrap_or(false), unified_code: value.unified_code, unified_message: value.unified_message, error_category: value.error_category, - clear_pan_possible: value.clear_pan_possible, + clear_pan_possible: value + .feature_data + .get_retry_feature_data() + .map(|data| data.is_clear_pan_possible()) + .unwrap_or(false), + feature_data: Some(value.feature_data), + feature: value.feature, } } } diff --git a/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/down.sql b/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/down.sql new file mode 100644 index 0000000000..35bcb7d37f --- /dev/null +++ b/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE gateway_status_map DROP COLUMN IF EXISTS feature_data, + DROP COLUMN IF EXISTS feature; \ No newline at end of file diff --git a/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/up.sql b/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/up.sql new file mode 100644 index 0000000000..1e53a532e8 --- /dev/null +++ b/migrations/2025-04-07-133030_add_feature_data_column_to_gsm/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE gateway_status_map +ADD COLUMN IF NOT EXISTS feature_data JSONB DEFAULT NULL, + ADD COLUMN IF NOT EXISTS feature VARCHAR(64) DEFAULT NULL; \ No newline at end of file