Bug: [FEATURE] add translation to unified error messages in API response and payout link



### Feature Description

Connector can fail due to numerous reasons and usually returns an error code along with an error message. These error messages are specific to the underlying connectors. However, these errors can be logically categorised under a single error code + message combination. This ensures the errors are better understood across different connectors. Once these errors are unified, they can be translated as well based on the requested locale for both payout APIs and payout links.

### Possible Implementation

1. Obtain a list of all the errors across different payout connectors
2. Form unified codes and messages (push them in GSM table)
3. Form translated messages (push them in GSM table)
4. Whenever an error occurs, `unified_code` and `unified_message` is updated in the payout entry (based on DB lookup in GSM)
5. Whenever the payout is requested, the unified error code and message is translated (based on DB lookup in GSM) for the requested locale and attached in the response / link

### Have you spent some time checking if this feature request has been raised before?

- [X] I checked and didn't find a similar issue

### Have you read the Contributing Guidelines?

- [X] I have read the [Contributing Guidelines](https://github.com/juspay/hyperswitch/blob/main/docs/CONTRIBUTING.md)

### Are you willing to submit a PR?

Yes, I am willing to submit a PR!
diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json
index acff517e107..e7dbbcdabe6 100644
--- a/api-reference-v2/openapi_spec.json
+++ b/api-reference-v2/openapi_spec.json
@@ -15207,13 +15207,17 @@
           },
           "unified_code": {
             "type": "string",
-            "description": "error code unified across the connectors is received here if there was an error while calling connector",
-            "nullable": true
+            "description": "(This field is not live yet)\nError code unified across the connectors is received here in case of errors while calling the underlying connector",
+            "example": "UE_000",
+            "nullable": true,
+            "maxLength": 255
           },
           "unified_message": {
             "type": "string",
-            "description": "error message unified across the connectors is received here if there was an error while calling connector",
-            "nullable": true
+            "description": "(This field is not live yet)\nError message unified across the connectors is received here in case of errors while calling the underlying connector",
+            "example": "Invalid card details",
+            "nullable": true,
+            "maxLength": 1024
           }
         }
       },
@@ -15727,6 +15731,20 @@
             "example": "+1",
             "nullable": true,
             "maxLength": 255
+          },
+          "unified_code": {
+            "type": "string",
+            "description": "(This field is not live yet)\nError code unified across the connectors is received here in case of errors while calling the underlying connector",
+            "example": "UE_000",
+            "nullable": true,
+            "maxLength": 255
+          },
+          "unified_message": {
+            "type": "string",
+            "description": "(This field is not live yet)\nError message unified across the connectors is received here in case of errors while calling the underlying connector",
+            "example": "Invalid card details",
+            "nullable": true,
+            "maxLength": 1024
           }
         },
         "additionalProperties": false
diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs
index 01bb63642be..10771df4ccd 100644
--- a/crates/api_models/src/payouts.rs
+++ b/crates/api_models/src/payouts.rs
@@ -5,6 +5,7 @@ use common_utils::{
     consts::default_payouts_list_limit,
     crypto, id_type, link_utils,
     pii::{self, Email},
+    types::{UnifiedCode, UnifiedMessage},
 };
 use masking::Secret;
 use router_derive::FlatStruct;
@@ -383,7 +384,7 @@ pub struct Venmo {
     pub telephone_number: Option<Secret<String>>,
 }
 
-#[derive(Debug, ToSchema, Clone, Serialize)]
+#[derive(Debug, ToSchema, Clone, Serialize, router_derive::PolymorphicSchema)]
 #[serde(deny_unknown_fields)]
 pub struct PayoutCreateResponse {
     /// Unique identifier for the payout. This ensures idempotency for multiple payouts
@@ -535,6 +536,18 @@ pub struct PayoutCreateResponse {
     /// Customer's phone country code. _Deprecated: Use customer object instead._
     #[schema(deprecated, max_length = 255, example = "+1")]
     pub phone_country_code: Option<String>,
+
+    /// (This field is not live yet)
+    /// Error code unified across the connectors is received here in case of errors while calling the underlying connector
+    #[remove_in(PayoutCreateResponse)]
+    #[schema(value_type = Option<String>, max_length = 255, example = "UE_000")]
+    pub unified_code: Option<UnifiedCode>,
+
+    /// (This field is not live yet)
+    /// Error message unified across the connectors is received here in case of errors while calling the underlying connector
+    #[remove_in(PayoutCreateResponse)]
+    #[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(
@@ -568,10 +581,16 @@ pub struct PayoutAttemptResponse {
     pub connector_transaction_id: Option<String>,
     /// If the payout was cancelled the reason provided here
     pub cancellation_reason: Option<String>,
-    /// error code unified across the connectors is received here if there was an error while calling connector
-    pub unified_code: Option<String>,
-    /// error message unified across the connectors is received here if there was an error while calling connector
-    pub unified_message: Option<String>,
+    /// (This field is not live yet)
+    /// Error code unified across the connectors is received here in case of errors while calling the underlying connector
+    #[remove_in(PayoutAttemptResponse)]
+    #[schema(value_type = Option<String>, max_length = 255, example = "UE_000")]
+    pub unified_code: Option<UnifiedCode>,
+    /// (This field is not live yet)
+    /// Error message unified across the connectors is received here in case of errors while calling the underlying connector
+    #[remove_in(PayoutAttemptResponse)]
+    #[schema(value_type = Option<String>, max_length = 1024, example = "Invalid card details")]
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(Default, Debug, Clone, Deserialize, ToSchema)]
@@ -819,8 +838,8 @@ pub struct PayoutLinkStatusDetails {
     pub session_expiry: PrimitiveDateTime,
     pub return_url: Option<url::Url>,
     pub status: api_enums::PayoutStatus,
-    pub error_code: Option<String>,
-    pub error_message: Option<String>,
+    pub error_code: Option<UnifiedCode>,
+    pub error_message: Option<UnifiedMessage>,
     #[serde(flatten)]
     pub ui_config: link_utils::GenericLinkUiConfigFormData,
     pub test_mode: bool,
diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs
index 299e70dc4c9..70f00ad9b3f 100644
--- a/crates/common_utils/src/consts.rs
+++ b/crates/common_utils/src/consts.rs
@@ -146,3 +146,6 @@ pub const ROLE_ID_ORGANIZATION_ADMIN: &str = "org_admin";
 pub const ROLE_ID_INTERNAL_VIEW_ONLY_USER: &str = "internal_view_only";
 /// Role ID for Internal Admin
 pub const ROLE_ID_INTERNAL_ADMIN: &str = "internal_admin";
+
+/// Payout flow identifier used for performing GSM operations
+pub const PAYOUT_FLOW_STR: &str = "payout_flow";
diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs
index 7f2f844b03f..6acf5000ced 100644
--- a/crates/common_utils/src/types.rs
+++ b/crates/common_utils/src/types.rs
@@ -32,7 +32,7 @@ use utoipa::ToSchema;
 
 use crate::{
     consts,
-    errors::{CustomResult, ParsingError, PercentageError},
+    errors::{CustomResult, ParsingError, PercentageError, ValidationError},
 };
 /// Represents Percentage Value between 0 and 100 both inclusive
 #[derive(Clone, Default, Debug, PartialEq, Serialize)]
@@ -767,3 +767,107 @@ where
         self.0.to_sql(out)
     }
 }
+
+/// Domain type for unified code
+#[derive(
+    Debug, Clone, PartialEq, Eq, Queryable, serde::Deserialize, serde::Serialize, AsExpression,
+)]
+#[diesel(sql_type = sql_types::Text)]
+pub struct UnifiedCode(pub String);
+
+impl TryFrom<String> for UnifiedCode {
+    type Error = error_stack::Report<ValidationError>;
+    fn try_from(src: String) -> Result<Self, Self::Error> {
+        if src.len() > 255 {
+            Err(report!(ValidationError::InvalidValue {
+                message: "unified_code's length should not exceed 255 characters".to_string()
+            }))
+        } else {
+            Ok(Self(src))
+        }
+    }
+}
+
+impl<DB> Queryable<sql_types::Text, DB> for UnifiedCode
+where
+    DB: Backend,
+    Self: FromSql<sql_types::Text, DB>,
+{
+    type Row = Self;
+
+    fn build(row: Self::Row) -> deserialize::Result<Self> {
+        Ok(row)
+    }
+}
+impl<DB> FromSql<sql_types::Text, DB> for UnifiedCode
+where
+    DB: Backend,
+    String: FromSql<sql_types::Text, DB>,
+{
+    fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
+        let val = String::from_sql(bytes)?;
+        Ok(Self::try_from(val)?)
+    }
+}
+
+impl<DB> ToSql<sql_types::Text, DB> for UnifiedCode
+where
+    DB: Backend,
+    String: ToSql<sql_types::Text, DB>,
+{
+    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
+        self.0.to_sql(out)
+    }
+}
+
+/// Domain type for unified messages
+#[derive(
+    Debug, Clone, PartialEq, Eq, Queryable, serde::Deserialize, serde::Serialize, AsExpression,
+)]
+#[diesel(sql_type = sql_types::Text)]
+pub struct UnifiedMessage(pub String);
+
+impl TryFrom<String> for UnifiedMessage {
+    type Error = error_stack::Report<ValidationError>;
+    fn try_from(src: String) -> Result<Self, Self::Error> {
+        if src.len() > 1024 {
+            Err(report!(ValidationError::InvalidValue {
+                message: "unified_message's length should not exceed 1024 characters".to_string()
+            }))
+        } else {
+            Ok(Self(src))
+        }
+    }
+}
+
+impl<DB> Queryable<sql_types::Text, DB> for UnifiedMessage
+where
+    DB: Backend,
+    Self: FromSql<sql_types::Text, DB>,
+{
+    type Row = Self;
+
+    fn build(row: Self::Row) -> deserialize::Result<Self> {
+        Ok(row)
+    }
+}
+impl<DB> FromSql<sql_types::Text, DB> for UnifiedMessage
+where
+    DB: Backend,
+    String: FromSql<sql_types::Text, DB>,
+{
+    fn from_sql(bytes: DB::RawValue<'_>) -> deserialize::Result<Self> {
+        let val = String::from_sql(bytes)?;
+        Ok(Self::try_from(val)?)
+    }
+}
+
+impl<DB> ToSql<sql_types::Text, DB> for UnifiedMessage
+where
+    DB: Backend,
+    String: ToSql<sql_types::Text, DB>,
+{
+    fn to_sql<'b>(&'b self, out: &mut Output<'b, '_, DB>) -> diesel::serialize::Result {
+        self.0.to_sql(out)
+    }
+}
diff --git a/crates/diesel_models/src/payout_attempt.rs b/crates/diesel_models/src/payout_attempt.rs
index 619e694f411..edb60bc1f44 100644
--- a/crates/diesel_models/src/payout_attempt.rs
+++ b/crates/diesel_models/src/payout_attempt.rs
@@ -1,3 +1,4 @@
+use common_utils::types::{UnifiedCode, UnifiedMessage};
 use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable};
 use serde::{self, Deserialize, Serialize};
 use time::PrimitiveDateTime;
@@ -30,6 +31,8 @@ pub struct PayoutAttempt {
     pub profile_id: common_utils::id_type::ProfileId,
     pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
     pub routing_info: Option<serde_json::Value>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(
@@ -66,6 +69,8 @@ pub struct PayoutAttemptNew {
     pub profile_id: common_utils::id_type::ProfileId,
     pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
     pub routing_info: Option<serde_json::Value>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -76,6 +81,8 @@ pub enum PayoutAttemptUpdate {
         error_message: Option<String>,
         error_code: Option<String>,
         is_eligible: Option<bool>,
+        unified_code: Option<UnifiedCode>,
+        unified_message: Option<UnifiedMessage>,
     },
     PayoutTokenUpdate {
         payout_token: String,
@@ -110,6 +117,8 @@ pub struct PayoutAttemptUpdateInternal {
     pub address_id: Option<String>,
     pub customer_id: Option<common_utils::id_type::CustomerId>,
     pub merchant_connector_id: Option<common_utils::id_type::MerchantConnectorAccountId>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 impl Default for PayoutAttemptUpdateInternal {
@@ -129,6 +138,8 @@ impl Default for PayoutAttemptUpdateInternal {
             last_modified_at: common_utils::date_time::now(),
             address_id: None,
             customer_id: None,
+            unified_code: None,
+            unified_message: None,
         }
     }
 }
@@ -146,12 +157,16 @@ impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
             } => Self {
                 connector_payout_id,
                 status: Some(status),
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
                 ..Default::default()
             },
             PayoutAttemptUpdate::BusinessUpdate {
@@ -197,6 +212,8 @@ impl PayoutAttemptUpdate {
             address_id,
             customer_id,
             merchant_connector_id,
+            unified_code,
+            unified_message,
         } = self.into();
         PayoutAttempt {
             payout_token: payout_token.or(source.payout_token),
@@ -213,6 +230,8 @@ impl PayoutAttemptUpdate {
             address_id: address_id.or(source.address_id),
             customer_id: customer_id.or(source.customer_id),
             merchant_connector_id: merchant_connector_id.or(source.merchant_connector_id),
+            unified_code: unified_code.or(source.unified_code),
+            unified_message: unified_message.or(source.unified_message),
             ..source
         }
     }
diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs
index 6e8918f02a4..b98b19abb46 100644
--- a/crates/diesel_models/src/schema.rs
+++ b/crates/diesel_models/src/schema.rs
@@ -1047,6 +1047,10 @@ diesel::table! {
         #[max_length = 32]
         merchant_connector_id -> Nullable<Varchar>,
         routing_info -> Nullable<Jsonb>,
+        #[max_length = 255]
+        unified_code -> Nullable<Varchar>,
+        #[max_length = 1024]
+        unified_message -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs
index bd64c4bb002..2e007a14840 100644
--- a/crates/diesel_models/src/schema_v2.rs
+++ b/crates/diesel_models/src/schema_v2.rs
@@ -1003,6 +1003,10 @@ diesel::table! {
         #[max_length = 32]
         merchant_connector_id -> Nullable<Varchar>,
         routing_info -> Nullable<Jsonb>,
+        #[max_length = 255]
+        unified_code -> Nullable<Varchar>,
+        #[max_length = 1024]
+        unified_message -> Nullable<Varchar>,
     }
 }
 
diff --git a/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs b/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
index f747eb96c4a..9c516142844 100644
--- a/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
+++ b/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs
@@ -1,6 +1,9 @@
 use api_models::enums::PayoutConnectors;
 use common_enums as storage_enums;
-use common_utils::id_type;
+use common_utils::{
+    id_type,
+    types::{UnifiedCode, UnifiedMessage},
+};
 use serde::{Deserialize, Serialize};
 use storage_enums::MerchantStorageScheme;
 use time::PrimitiveDateTime;
@@ -78,6 +81,8 @@ pub struct PayoutAttempt {
     pub profile_id: id_type::ProfileId,
     pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
     pub routing_info: Option<serde_json::Value>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(Clone, Debug, PartialEq)]
@@ -101,6 +106,8 @@ pub struct PayoutAttemptNew {
     pub profile_id: id_type::ProfileId,
     pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
     pub routing_info: Option<serde_json::Value>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 #[derive(Debug, Clone)]
@@ -111,6 +118,9 @@ pub enum PayoutAttemptUpdate {
         error_message: Option<String>,
         error_code: Option<String>,
         is_eligible: Option<bool>,
+
+        unified_code: Option<UnifiedCode>,
+        unified_message: Option<UnifiedMessage>,
     },
     PayoutTokenUpdate {
         payout_token: String,
@@ -143,6 +153,8 @@ pub struct PayoutAttemptUpdateInternal {
     pub address_id: Option<String>,
     pub customer_id: Option<id_type::CustomerId>,
     pub merchant_connector_id: Option<id_type::MerchantConnectorAccountId>,
+    pub unified_code: Option<UnifiedCode>,
+    pub unified_message: Option<UnifiedMessage>,
 }
 
 impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
@@ -158,12 +170,16 @@ impl From<PayoutAttemptUpdate> for PayoutAttemptUpdateInternal {
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
             } => Self {
                 connector_payout_id,
                 status: Some(status),
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
                 ..Default::default()
             },
             PayoutAttemptUpdate::BusinessUpdate {
diff --git a/crates/router/src/connector/adyenplatform/transformers/payouts.rs b/crates/router/src/connector/adyenplatform/transformers/payouts.rs
index e0ec8f9e18b..09772fc87de 100644
--- a/crates/router/src/connector/adyenplatform/transformers/payouts.rs
+++ b/crates/router/src/connector/adyenplatform/transformers/payouts.rs
@@ -270,14 +270,20 @@ impl<F> TryFrom<types::PayoutsResponseRouterData<F, AdyenTransferResponse>>
         item: types::PayoutsResponseRouterData<F, AdyenTransferResponse>,
     ) -> Result<Self, Self::Error> {
         let response: AdyenTransferResponse = item.response;
+        let status = enums::PayoutStatus::from(response.status);
+
+        let error_code = match status {
+            enums::PayoutStatus::Ineligible => Some(response.reason),
+            _ => None,
+        };
 
         Ok(Self {
             response: Ok(types::PayoutsResponseData {
-                status: Some(enums::PayoutStatus::from(response.status)),
+                status: Some(status),
                 connector_payout_id: Some(response.id),
                 payout_eligible: None,
                 should_add_next_step_to_process_tracker: false,
-                error_code: None,
+                error_code,
                 error_message: None,
             }),
             ..item.data
diff --git a/crates/router/src/core/generic_link/payout_link/status/script.js b/crates/router/src/core/generic_link/payout_link/status/script.js
index 473af49b2e7..819d7e63c37 100644
--- a/crates/router/src/core/generic_link/payout_link/status/script.js
+++ b/crates/router/src/core/generic_link/payout_link/status/script.js
@@ -121,10 +121,10 @@ function renderStatusDetails(payoutDetails) {
     "{{i18n_ref_id_text}}": payoutDetails.payout_id,
   };
   if (typeof payoutDetails.error_code === "string") {
-    // resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code;
+    resourceInfo["{{i18n_error_code_text}}"] = payoutDetails.error_code;
   }
   if (typeof payoutDetails.error_message === "string") {
-    // resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message;
+    resourceInfo["{{i18n_error_message}}"] = payoutDetails.error_message;
   }
   var resourceNode = document.createElement("div");
   resourceNode.id = "resource-info-container";
diff --git a/crates/router/src/core/generic_link/payout_link/status/styles.css b/crates/router/src/core/generic_link/payout_link/status/styles.css
index a10bfe7e3c0..11c6e68dc41 100644
--- a/crates/router/src/core/generic_link/payout_link/status/styles.css
+++ b/crates/router/src/core/generic_link/payout_link/status/styles.css
@@ -80,11 +80,12 @@ body {
 #resource-info-container {
   width: 100%;
   border-top: 1px solid rgb(231, 234, 241);
-  padding: 20px 10px;
+  padding: 20px 0;
 }
 #resource-info {
   display: flex;
   align-items: center;
+  margin: 0 2.5rem;
 }
 #info-key {
   text-align: right;
diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs
index a153d071088..b175c8b7c03 100644
--- a/crates/router/src/core/payout_link.rs
+++ b/crates/router/src/core/payout_link.rs
@@ -17,7 +17,10 @@ use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData};
 use super::errors::{RouterResponse, StorageErrorExt};
 use crate::{
     configs::settings::{PaymentMethodFilterKey, PaymentMethodFilters},
-    core::{payments::helpers, payouts::validator},
+    core::{
+        payments::helpers as payment_helpers,
+        payouts::{helpers as payout_helpers, validator},
+    },
     errors,
     routes::{app::StorageInterface, SessionState},
     services,
@@ -271,6 +274,14 @@ pub async fn initiate_payout_link(
 
         // Send back status page
         (_, link_utils::PayoutLinkStatus::Submitted) => {
+            let translated_unified_message =
+                payout_helpers::get_translated_unified_code_and_message(
+                    &state,
+                    payout_attempt.unified_code.as_ref(),
+                    payout_attempt.unified_message.as_ref(),
+                    &locale,
+                )
+                .await?;
             let js_data = payouts::PayoutLinkStatusDetails {
                 payout_link_id: payout_link.link_id,
                 payout_id: payout_link.primary_reference,
@@ -284,8 +295,8 @@ pub async fn initiate_payout_link(
                     .change_context(errors::ApiErrorResponse::InternalServerError)
                     .attach_printable("Failed to parse payout status link's return URL")?,
                 status: payout.status,
-                error_code: payout_attempt.error_code,
-                error_message: payout_attempt.error_message,
+                error_code: payout_attempt.unified_code,
+                error_message: translated_unified_message,
                 ui_config: ui_config_data,
                 test_mode: link_data.test_mode.unwrap_or(false),
             };
@@ -338,7 +349,7 @@ pub async fn filter_payout_methods(
         .await
         .to_not_found_response(errors::ApiErrorResponse::MerchantAccountNotFound)?;
     // Filter MCAs based on profile_id and connector_type
-    let filtered_mcas = helpers::filter_mca_based_on_profile_and_connector_type(
+    let filtered_mcas = payment_helpers::filter_mca_based_on_profile_and_connector_type(
         all_mcas,
         &payout.profile_id,
         common_enums::ConnectorType::PayoutProcessor,
diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs
index 85d5b78521f..d0c0b2d3cde 100644
--- a/crates/router/src/core/payouts.rs
+++ b/crates/router/src/core/payouts.rs
@@ -16,7 +16,7 @@ use common_utils::{
     ext_traits::{AsyncExt, ValueExt},
     id_type::CustomerId,
     link_utils::{GenericLinkStatus, GenericLinkUiConfig, PayoutLinkData, PayoutLinkStatus},
-    types::MinorUnit,
+    types::{MinorUnit, UnifiedCode, UnifiedMessage},
 };
 use diesel_models::{
     enums as storage_enums,
@@ -71,6 +71,7 @@ pub struct PayoutData {
     pub profile_id: common_utils::id_type::ProfileId,
     pub should_terminate: bool,
     pub payout_link: Option<PayoutLink>,
+    pub current_locale: String,
 }
 
 // ********************************************** CORE FLOWS **********************************************
@@ -358,7 +359,7 @@ pub async fn payouts_create_core(
         .await?
     };
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 #[instrument(skip_all)]
@@ -367,6 +368,7 @@ pub async fn payouts_confirm_core(
     merchant_account: domain::MerchantAccount,
     key_store: domain::MerchantKeyStore,
     req: payouts::PayoutCreateRequest,
+    locale: &str,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     let mut payout_data = make_payout_data(
         &state,
@@ -374,6 +376,7 @@ pub async fn payouts_confirm_core(
         None,
         &key_store,
         &payouts::PayoutRequest::PayoutCreateRequest(req.to_owned()),
+        locale,
     )
     .await?;
     let payout_attempt = payout_data.payout_attempt.to_owned();
@@ -429,7 +432,7 @@ pub async fn payouts_confirm_core(
     )
     .await?;
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 pub async fn payouts_update_core(
@@ -437,6 +440,7 @@ pub async fn payouts_update_core(
     merchant_account: domain::MerchantAccount,
     key_store: domain::MerchantKeyStore,
     req: payouts::PayoutCreateRequest,
+    locale: &str,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     let payout_id = req.payout_id.clone().get_required_value("payout_id")?;
     let mut payout_data = make_payout_data(
@@ -445,6 +449,7 @@ pub async fn payouts_update_core(
         None,
         &key_store,
         &payouts::PayoutRequest::PayoutCreateRequest(req.to_owned()),
+        locale,
     )
     .await?;
 
@@ -509,7 +514,7 @@ pub async fn payouts_update_core(
         .await?;
     }
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 #[instrument(skip_all)]
@@ -519,6 +524,7 @@ pub async fn payouts_retrieve_core(
     profile_id: Option<common_utils::id_type::ProfileId>,
     key_store: domain::MerchantKeyStore,
     req: payouts::PayoutRetrieveRequest,
+    locale: &str,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     let mut payout_data = make_payout_data(
         &state,
@@ -526,6 +532,7 @@ pub async fn payouts_retrieve_core(
         profile_id,
         &key_store,
         &payouts::PayoutRequest::PayoutRetrieveRequest(req.to_owned()),
+        locale,
     )
     .await?;
     let payout_attempt = payout_data.payout_attempt.to_owned();
@@ -553,7 +560,7 @@ pub async fn payouts_retrieve_core(
         .await?;
     }
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 #[instrument(skip_all)]
@@ -562,6 +569,7 @@ pub async fn payouts_cancel_core(
     merchant_account: domain::MerchantAccount,
     key_store: domain::MerchantKeyStore,
     req: payouts::PayoutActionRequest,
+    locale: &str,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     let mut payout_data = make_payout_data(
         &state,
@@ -569,6 +577,7 @@ pub async fn payouts_cancel_core(
         None,
         &key_store,
         &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()),
+        locale,
     )
     .await?;
 
@@ -593,6 +602,8 @@ pub async fn payouts_cancel_core(
             error_message: Some("Cancelled by user".to_string()),
             error_code: None,
             is_eligible: None,
+            unified_code: None,
+            unified_message: None,
         };
         payout_data.payout_attempt = state
             .store
@@ -643,7 +654,7 @@ pub async fn payouts_cancel_core(
             .attach_printable("Payout cancellation failed for given Payout request")?;
     }
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 #[instrument(skip_all)]
@@ -652,6 +663,7 @@ pub async fn payouts_fulfill_core(
     merchant_account: domain::MerchantAccount,
     key_store: domain::MerchantKeyStore,
     req: payouts::PayoutActionRequest,
+    locale: &str,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
     let mut payout_data = make_payout_data(
         &state,
@@ -659,6 +671,7 @@ pub async fn payouts_fulfill_core(
         None,
         &key_store,
         &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()),
+        locale,
     )
     .await?;
 
@@ -735,7 +748,7 @@ pub async fn payouts_fulfill_core(
         }));
     }
 
-    response_handler(&merchant_account, &payout_data).await
+    response_handler(&state, &merchant_account, &payout_data).await
 }
 
 #[cfg(all(feature = "olap", feature = "v2", feature = "customer_v2"))]
@@ -745,6 +758,7 @@ pub async fn payouts_list_core(
     _profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
     _key_store: domain::MerchantKeyStore,
     _constraints: payouts::PayoutListConstraints,
+    _locale: &str,
 ) -> RouterResponse<payouts::PayoutListResponse> {
     todo!()
 }
@@ -760,6 +774,7 @@ pub async fn payouts_list_core(
     profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
     key_store: domain::MerchantKeyStore,
     constraints: payouts::PayoutListConstraints,
+    _locale: &str,
 ) -> RouterResponse<payouts::PayoutListResponse> {
     validator::validate_payout_list_request(&constraints)?;
     let merchant_id = merchant_account.get_id();
@@ -878,6 +893,7 @@ pub async fn payouts_filtered_list_core(
     profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
     key_store: domain::MerchantKeyStore,
     filters: payouts::PayoutListFilterConstraints,
+    _locale: &str,
 ) -> RouterResponse<payouts::PayoutListResponse> {
     let limit = &filters.limit;
     validator::validate_payout_list_request_for_joins(*limit)?;
@@ -981,6 +997,7 @@ pub async fn payouts_list_available_filters_core(
     merchant_account: domain::MerchantAccount,
     profile_id_list: Option<Vec<common_utils::id_type::ProfileId>>,
     time_range: api::TimeRange,
+    _locale: &str,
 ) -> RouterResponse<api::PayoutListFilters> {
     let db = state.store.as_ref();
     let payouts = db
@@ -1293,6 +1310,8 @@ pub async fn create_recipient(
                         error_code: None,
                         error_message: None,
                         is_eligible: recipient_create_data.payout_eligible,
+                        unified_code: None,
+                        unified_message: None,
                     };
                     payout_data.payout_attempt = db
                         .update_payout_attempt(
@@ -1406,6 +1425,8 @@ pub async fn check_payout_eligibility(
                 error_code: None,
                 error_message: None,
                 is_eligible: payout_response_data.payout_eligible,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1437,12 +1458,34 @@ pub async fn check_payout_eligibility(
         }
         Err(err) => {
             let status = storage_enums::PayoutStatus::Failed;
+            let (error_code, error_message) = (Some(err.code), Some(err.message));
+            let (unified_code, unified_message) = helpers::get_gsm_record(
+                state,
+                error_code.clone(),
+                error_message.clone(),
+                payout_data.payout_attempt.connector.clone(),
+                consts::PAYOUT_FLOW_STR,
+            )
+            .await
+            .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                 status,
-                error_code: Some(err.code),
-                error_message: Some(err.message),
+                error_code,
+                error_message,
                 is_eligible: Some(false),
+                unified_code: unified_code
+                    .map(UnifiedCode::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_code",
+                    })?,
+                unified_message: unified_message
+                    .map(UnifiedMessage::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_message",
+                    })?,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1497,6 +1540,8 @@ pub async fn complete_create_payout(
                 error_code: None,
                 error_message: None,
                 is_eligible: None,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1591,6 +1636,8 @@ pub async fn create_payout(
                 error_code: None,
                 error_message: None,
                 is_eligible: payout_response_data.payout_eligible,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1622,12 +1669,34 @@ pub async fn create_payout(
         }
         Err(err) => {
             let status = storage_enums::PayoutStatus::Failed;
+            let (error_code, error_message) = (Some(err.code), Some(err.message));
+            let (unified_code, unified_message) = helpers::get_gsm_record(
+                state,
+                error_code.clone(),
+                error_message.clone(),
+                payout_data.payout_attempt.connector.clone(),
+                consts::PAYOUT_FLOW_STR,
+            )
+            .await
+            .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                 status,
-                error_code: Some(err.code),
-                error_message: Some(err.message),
+                error_code,
+                error_message,
                 is_eligible: None,
+                unified_code: unified_code
+                    .map(UnifiedCode::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_code",
+                    })?,
+                unified_message: unified_message
+                    .map(UnifiedMessage::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_message",
+                    })?,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1774,12 +1843,37 @@ pub async fn update_retrieve_payout_tracker<F, T>(
                 .unwrap_or(payout_attempt.status.to_owned());
 
             let updated_payout_attempt = if helpers::is_payout_err_state(status) {
+                let (error_code, error_message) = (
+                    payout_response_data.error_code.clone(),
+                    payout_response_data.error_message.clone(),
+                );
+                let (unified_code, unified_message) = helpers::get_gsm_record(
+                    state,
+                    error_code.clone(),
+                    error_message.clone(),
+                    payout_data.payout_attempt.connector.clone(),
+                    consts::PAYOUT_FLOW_STR,
+                )
+                .await
+                .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
                 storage::PayoutAttemptUpdate::StatusUpdate {
                     connector_payout_id: payout_response_data.connector_payout_id.clone(),
                     status,
-                    error_code: payout_response_data.error_code.clone(),
-                    error_message: payout_response_data.error_message.clone(),
+                    error_code,
+                    error_message,
                     is_eligible: payout_response_data.payout_eligible,
+                    unified_code: unified_code
+                        .map(UnifiedCode::try_from)
+                        .transpose()
+                        .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                            field_name: "unified_code",
+                        })?,
+                    unified_message: unified_message
+                        .map(UnifiedMessage::try_from)
+                        .transpose()
+                        .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                            field_name: "unified_message",
+                        })?,
                 }
             } else {
                 storage::PayoutAttemptUpdate::StatusUpdate {
@@ -1788,6 +1882,8 @@ pub async fn update_retrieve_payout_tracker<F, T>(
                     error_code: None,
                     error_message: None,
                     is_eligible: payout_response_data.payout_eligible,
+                    unified_code: None,
+                    unified_message: None,
                 }
             };
 
@@ -1886,6 +1982,8 @@ pub async fn create_recipient_disburse_account(
                 error_code: None,
                 error_message: None,
                 is_eligible: payout_response_data.payout_eligible,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1899,12 +1997,34 @@ pub async fn create_recipient_disburse_account(
                 .attach_printable("Error updating payout_attempt in db")?;
         }
         Err(err) => {
+            let (error_code, error_message) = (Some(err.code), Some(err.message));
+            let (unified_code, unified_message) = helpers::get_gsm_record(
+                state,
+                error_code.clone(),
+                error_message.clone(),
+                payout_data.payout_attempt.connector.clone(),
+                consts::PAYOUT_FLOW_STR,
+            )
+            .await
+            .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                 status: storage_enums::PayoutStatus::Failed,
-                error_code: Some(err.code),
-                error_message: Some(err.message),
+                error_code,
+                error_message,
                 is_eligible: None,
+                unified_code: unified_code
+                    .map(UnifiedCode::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_code",
+                    })?,
+                unified_message: unified_message
+                    .map(UnifiedMessage::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_message",
+                    })?,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1964,6 +2084,8 @@ pub async fn cancel_payout(
                 error_code: None,
                 error_message: None,
                 is_eligible: payout_response_data.payout_eligible,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -1988,12 +2110,34 @@ pub async fn cancel_payout(
         }
         Err(err) => {
             let status = storage_enums::PayoutStatus::Failed;
+            let (error_code, error_message) = (Some(err.code), Some(err.message));
+            let (unified_code, unified_message) = helpers::get_gsm_record(
+                state,
+                error_code.clone(),
+                error_message.clone(),
+                payout_data.payout_attempt.connector.clone(),
+                consts::PAYOUT_FLOW_STR,
+            )
+            .await
+            .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                 status,
-                error_code: Some(err.code),
-                error_message: Some(err.message),
+                error_code,
+                error_message,
                 is_eligible: None,
+                unified_code: unified_code
+                    .map(UnifiedCode::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_code",
+                    })?,
+                unified_message: unified_message
+                    .map(UnifiedMessage::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_message",
+                    })?,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -2075,6 +2219,8 @@ pub async fn fulfill_payout(
                 error_code: None,
                 error_message: None,
                 is_eligible: payout_response_data.payout_eligible,
+                unified_code: None,
+                unified_message: None,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -2131,12 +2277,34 @@ pub async fn fulfill_payout(
         }
         Err(err) => {
             let status = storage_enums::PayoutStatus::Failed;
+            let (error_code, error_message) = (Some(err.code), Some(err.message));
+            let (unified_code, unified_message) = helpers::get_gsm_record(
+                state,
+                error_code.clone(),
+                error_message.clone(),
+                payout_data.payout_attempt.connector.clone(),
+                consts::PAYOUT_FLOW_STR,
+            )
+            .await
+            .map_or((None, None), |gsm| (gsm.unified_code, gsm.unified_message));
             let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id: payout_data.payout_attempt.connector_payout_id.to_owned(),
                 status,
-                error_code: Some(err.code),
-                error_message: Some(err.message),
+                error_code,
+                error_message,
                 is_eligible: None,
+                unified_code: unified_code
+                    .map(UnifiedCode::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_code",
+                    })?,
+                unified_message: unified_message
+                    .map(UnifiedMessage::try_from)
+                    .transpose()
+                    .change_context(errors::ApiErrorResponse::InvalidDataValue {
+                        field_name: "unified_message",
+                    })?,
             };
             payout_data.payout_attempt = db
                 .update_payout_attempt(
@@ -2165,6 +2333,7 @@ pub async fn fulfill_payout(
 }
 
 pub async fn response_handler(
+    state: &SessionState,
     merchant_account: &domain::MerchantAccount,
     payout_data: &PayoutData,
 ) -> RouterResponse<payouts::PayoutCreateResponse> {
@@ -2176,12 +2345,20 @@ pub async fn response_handler(
     let customer_id = payouts.customer_id;
     let billing = billing_address.as_ref().map(From::from);
 
+    let translated_unified_message = helpers::get_translated_unified_code_and_message(
+        state,
+        payout_attempt.unified_code.as_ref(),
+        payout_attempt.unified_message.as_ref(),
+        &payout_data.current_locale,
+    )
+    .await?;
+
     let response = api::PayoutCreateResponse {
         payout_id: payouts.payout_id.to_owned(),
         merchant_id: merchant_account.get_id().to_owned(),
         amount: payouts.amount,
         currency: payouts.destination_currency.to_owned(),
-        connector: payout_attempt.connector.to_owned(),
+        connector: payout_attempt.connector,
         payout_type: payouts.payout_type.to_owned(),
         billing,
         auto_fulfill: payouts.auto_fulfill,
@@ -2212,6 +2389,8 @@ pub async fn response_handler(
         connector_transaction_id: payout_attempt.connector_payout_id,
         priority: payouts.priority,
         attempts: None,
+        unified_code: payout_attempt.unified_code,
+        unified_message: translated_unified_message,
         payout_link: payout_link
             .map(|payout_link| {
                 url::Url::parse(payout_link.url.peek()).map(|link| PayoutLinkResponse {
@@ -2389,6 +2568,8 @@ pub async fn payout_create_db_entries(
         last_modified_at: common_utils::date_time::now(),
         merchant_connector_id: None,
         routing_info: None,
+        unified_code: None,
+        unified_message: None,
     };
     let payout_attempt = db
         .insert_payout_attempt(
@@ -2418,6 +2599,7 @@ pub async fn payout_create_db_entries(
         should_terminate: false,
         profile_id: profile_id.to_owned(),
         payout_link,
+        current_locale: locale.to_string(),
     })
 }
 
@@ -2428,6 +2610,7 @@ pub async fn make_payout_data(
     _auth_profile_id: Option<common_utils::id_type::ProfileId>,
     _key_store: &domain::MerchantKeyStore,
     _req: &payouts::PayoutRequest,
+    locale: &str,
 ) -> RouterResult<PayoutData> {
     todo!()
 }
@@ -2439,6 +2622,7 @@ pub async fn make_payout_data(
     auth_profile_id: Option<common_utils::id_type::ProfileId>,
     key_store: &domain::MerchantKeyStore,
     req: &payouts::PayoutRequest,
+    locale: &str,
 ) -> RouterResult<PayoutData> {
     let db = &*state.store;
     let merchant_id = merchant_account.get_id();
@@ -2594,6 +2778,7 @@ pub async fn make_payout_data(
         should_terminate: false,
         profile_id,
         payout_link,
+        current_locale: locale.to_string(),
     })
 }
 
diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs
index 11cb0c11761..b6f685ee2a1 100644
--- a/crates/router/src/core/payouts/helpers.rs
+++ b/crates/router/src/core/payouts/helpers.rs
@@ -8,7 +8,7 @@ use common_utils::{
     fp_utils, id_type, type_name,
     types::{
         keymanager::{Identifier, KeyManagerState},
-        MinorUnit,
+        MinorUnit, UnifiedCode, UnifiedMessage,
     },
 };
 #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))]
@@ -944,12 +944,13 @@ pub async fn get_gsm_record(
     error_code: Option<String>,
     error_message: Option<String>,
     connector_name: Option<String>,
-    flow: String,
+    flow: &str,
 ) -> Option<storage::gsm::GatewayStatusMap> {
+    let connector_name = connector_name.unwrap_or_default();
     let get_gsm = || async {
         state.store.find_gsm_rule(
-                connector_name.clone().unwrap_or_default(),
-                flow.clone(),
+                connector_name.clone(),
+                flow.to_string(),
                 "sub_flow".to_string(),
                 error_code.clone().unwrap_or_default(), // TODO: make changes in connector to get a mandatory code in case of success or error response
                 error_message.clone().unwrap_or_default(),
@@ -959,7 +960,7 @@ pub async fn get_gsm_record(
                 if err.current_context().is_db_not_found() {
                     logger::warn!(
                         "GSM miss for connector - {}, flow - {}, error_code - {:?}, error_message - {:?}",
-                        connector_name.unwrap_or_default(),
+                        connector_name,
                         flow,
                         error_code,
                         error_message
@@ -1265,3 +1266,29 @@ pub(super) fn get_customer_details_from_request(
         phone_country_code: customer_phone_code,
     }
 }
+
+pub async fn get_translated_unified_code_and_message(
+    state: &SessionState,
+    unified_code: Option<&UnifiedCode>,
+    unified_message: Option<&UnifiedMessage>,
+    locale: &str,
+) -> CustomResult<Option<UnifiedMessage>, errors::ApiErrorResponse> {
+    Ok(unified_code
+        .zip(unified_message)
+        .async_and_then(|(code, message)| async {
+            payment_helpers::get_unified_translation(
+                state,
+                code.0.clone(),
+                message.0.clone(),
+                locale.to_string(),
+            )
+            .await
+            .map(UnifiedMessage::try_from)
+        })
+        .await
+        .transpose()
+        .change_context(errors::ApiErrorResponse::InvalidDataValue {
+            field_name: "unified_message",
+        })?
+        .or_else(|| unified_message.cloned()))
+}
diff --git a/crates/router/src/core/payouts/retry.rs b/crates/router/src/core/payouts/retry.rs
index e190e627bdf..00c25b0d67e 100644
--- a/crates/router/src/core/payouts/retry.rs
+++ b/crates/router/src/core/payouts/retry.rs
@@ -190,12 +190,15 @@ pub async fn get_gsm(
     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());
-    let flow = "payout_flow".to_string();
 
-    Ok(
-        payouts::helpers::get_gsm_record(state, error_code, error_message, connector_name, flow)
-            .await,
+    Ok(payouts::helpers::get_gsm_record(
+        state,
+        error_code,
+        error_message,
+        connector_name,
+        common_utils::consts::PAYOUT_FLOW_STR,
     )
+    .await)
 }
 
 #[instrument(skip_all)]
@@ -295,6 +298,8 @@ pub async fn modify_trackers(
         last_modified_at: common_utils::date_time::now(),
         merchant_connector_id: None,
         routing_info: None,
+        unified_code: None,
+        unified_message: None,
     };
     payout_data.payout_attempt = db
         .insert_payout_attempt(
diff --git a/crates/router/src/core/payouts/transformers.rs b/crates/router/src/core/payouts/transformers.rs
index ed17eb71afa..080cf775b24 100644
--- a/crates/router/src/core/payouts/transformers.rs
+++ b/crates/router/src/core/payouts/transformers.rs
@@ -98,10 +98,12 @@ impl
             created: Some(payout.created_at),
             connector_transaction_id: attempt.connector_transaction_id.clone(),
             priority: payout.priority,
-            attempts: Some(vec![attempt]),
             billing: address,
             client_secret: None,
             payout_link: None,
+            unified_code: attempt.unified_code.clone(),
+            unified_message: attempt.unified_message.clone(),
+            attempts: Some(vec![attempt]),
             email: customer
                 .as_ref()
                 .and_then(|customer| customer.email.clone()),
diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs
index 20b07d8669f..dd7ccd87f38 100644
--- a/crates/router/src/core/webhooks/incoming.rs
+++ b/crates/router/src/core/webhooks/incoming.rs
@@ -689,6 +689,8 @@ async fn payouts_incoming_webhook_flow(
             error_message: None,
             error_code: None,
             is_eligible: payout_attempt.is_eligible,
+            unified_code: None,
+            unified_message: None,
         };
 
         let action_req =
@@ -696,9 +698,15 @@ async fn payouts_incoming_webhook_flow(
                 payout_id: payouts.payout_id.clone(),
             });
 
-        let payout_data =
-            payouts::make_payout_data(&state, &merchant_account, None, &key_store, &action_req)
-                .await?;
+        let payout_data = payouts::make_payout_data(
+            &state,
+            &merchant_account,
+            None,
+            &key_store,
+            &action_req,
+            common_utils::consts::DEFAULT_LOCALE,
+        )
+        .await?;
 
         let updated_payout_attempt = db
             .update_payout_attempt(
@@ -721,7 +729,7 @@ async fn payouts_incoming_webhook_flow(
         // If event is NOT an UnsupportedEvent, trigger Outgoing Webhook
         if let Some(outgoing_event_type) = event_type {
             let router_response =
-                payouts::response_handler(&merchant_account, &payout_data).await?;
+                payouts::response_handler(&state, &merchant_account, &payout_data).await?;
 
             let payout_create_response: payout_models::PayoutCreateResponse = match router_response
             {
diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs
index 50c18d71f2a..1b614330ebd 100644
--- a/crates/router/src/routes/payouts.rs
+++ b/crates/router/src/routes/payouts.rs
@@ -1,5 +1,6 @@
 use actix_web::{
     body::{BoxBody, MessageBody},
+    http::header::HeaderMap,
     web, HttpRequest, HttpResponse, Responder,
 };
 use common_enums::EntityType;
@@ -20,6 +21,14 @@ use crate::{
     types::api::payouts as payout_types,
 };
 
+fn get_locale_from_header(headers: &HeaderMap) -> String {
+    get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers)
+        .ok()
+        .flatten()
+        .map(|val| val.to_string())
+        .unwrap_or(consts::DEFAULT_LOCALE.to_string())
+}
+
 /// Payouts - Create
 #[instrument(skip_all, fields(flow = ?Flow::PayoutsCreate))]
 pub async fn payouts_create(
@@ -28,11 +37,8 @@ pub async fn payouts_create(
     json_payload: web::Json<payout_types::PayoutCreateRequest>,
 ) -> HttpResponse {
     let flow = Flow::PayoutsCreate;
-    let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), req.headers())
-        .ok()
-        .flatten()
-        .map(|val| val.to_string())
-        .unwrap_or(consts::DEFAULT_LOCALE.to_string());
+    let locale = get_locale_from_header(req.headers());
+
     Box::pin(api::server_wrap(
         flow,
         state,
@@ -60,6 +66,8 @@ pub async fn payouts_retrieve(
         merchant_id: query_params.merchant_id.to_owned(),
     };
     let flow = Flow::PayoutsRetrieve;
+    let locale = get_locale_from_header(req.headers());
+
     Box::pin(api::server_wrap(
         flow,
         state,
@@ -72,6 +80,7 @@ pub async fn payouts_retrieve(
                 auth.profile_id,
                 auth.key_store,
                 req,
+                &locale,
             )
         },
         auth::auth_type(
@@ -95,6 +104,7 @@ pub async fn payouts_update(
     json_payload: web::Json<payout_types::PayoutCreateRequest>,
 ) -> HttpResponse {
     let flow = Flow::PayoutsUpdate;
+    let locale = get_locale_from_header(req.headers());
     let payout_id = path.into_inner();
     let mut payout_update_payload = json_payload.into_inner();
     payout_update_payload.payout_id = Some(payout_id);
@@ -104,7 +114,7 @@ pub async fn payouts_update(
         &req,
         payout_update_payload,
         |state, auth, req, _| {
-            payouts_update_core(state, auth.merchant_account, auth.key_store, req)
+            payouts_update_core(state, auth.merchant_account, auth.key_store, req, &locale)
         },
         &auth::HeaderAuth(auth::ApiKeyAuth),
         api_locking::LockAction::NotApplicable,
@@ -130,6 +140,7 @@ pub async fn payouts_confirm(
             Ok(auth) => auth,
             Err(e) => return api::log_and_return_error_response(e),
         };
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -137,7 +148,7 @@ pub async fn payouts_confirm(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_confirm_core(state, auth.merchant_account, auth.key_store, req)
+            payouts_confirm_core(state, auth.merchant_account, auth.key_store, req, &locale)
         },
         &*auth_type,
         api_locking::LockAction::NotApplicable,
@@ -156,6 +167,7 @@ pub async fn payouts_cancel(
     let flow = Flow::PayoutsCancel;
     let mut payload = json_payload.into_inner();
     payload.payout_id = path.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -163,7 +175,7 @@ pub async fn payouts_cancel(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_cancel_core(state, auth.merchant_account, auth.key_store, req)
+            payouts_cancel_core(state, auth.merchant_account, auth.key_store, req, &locale)
         },
         &auth::HeaderAuth(auth::ApiKeyAuth),
         api_locking::LockAction::NotApplicable,
@@ -181,6 +193,7 @@ pub async fn payouts_fulfill(
     let flow = Flow::PayoutsFulfill;
     let mut payload = json_payload.into_inner();
     payload.payout_id = path.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -188,7 +201,7 @@ pub async fn payouts_fulfill(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req)
+            payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req, &locale)
         },
         &auth::HeaderAuth(auth::ApiKeyAuth),
         api_locking::LockAction::NotApplicable,
@@ -206,6 +219,7 @@ pub async fn payouts_list(
 ) -> HttpResponse {
     let flow = Flow::PayoutsList;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -213,7 +227,14 @@ pub async fn payouts_list(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_list_core(state, auth.merchant_account, None, auth.key_store, req)
+            payouts_list_core(
+                state,
+                auth.merchant_account,
+                None,
+                auth.key_store,
+                req,
+                &locale,
+            )
         },
         auth::auth_type(
             &auth::HeaderAuth(auth::ApiKeyAuth),
@@ -238,6 +259,7 @@ pub async fn payouts_list_profile(
 ) -> HttpResponse {
     let flow = Flow::PayoutsList;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -251,6 +273,7 @@ pub async fn payouts_list_profile(
                 auth.profile_id.map(|profile_id| vec![profile_id]),
                 auth.key_store,
                 req,
+                &locale,
             )
         },
         auth::auth_type(
@@ -276,6 +299,7 @@ pub async fn payouts_list_by_filter(
 ) -> HttpResponse {
     let flow = Flow::PayoutsList;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -283,7 +307,14 @@ pub async fn payouts_list_by_filter(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_filtered_list_core(state, auth.merchant_account, None, auth.key_store, req)
+            payouts_filtered_list_core(
+                state,
+                auth.merchant_account,
+                None,
+                auth.key_store,
+                req,
+                &locale,
+            )
         },
         auth::auth_type(
             &auth::HeaderAuth(auth::ApiKeyAuth),
@@ -308,6 +339,7 @@ pub async fn payouts_list_by_filter_profile(
 ) -> HttpResponse {
     let flow = Flow::PayoutsList;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -321,6 +353,7 @@ pub async fn payouts_list_by_filter_profile(
                 auth.profile_id.map(|profile_id| vec![profile_id]),
                 auth.key_store,
                 req,
+                &locale,
             )
         },
         auth::auth_type(
@@ -346,6 +379,7 @@ pub async fn payouts_list_available_filters_for_merchant(
 ) -> HttpResponse {
     let flow = Flow::PayoutsFilter;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -353,7 +387,7 @@ pub async fn payouts_list_available_filters_for_merchant(
         &req,
         payload,
         |state, auth, req, _| {
-            payouts_list_available_filters_core(state, auth.merchant_account, None, req)
+            payouts_list_available_filters_core(state, auth.merchant_account, None, req, &locale)
         },
         auth::auth_type(
             &auth::HeaderAuth(auth::ApiKeyAuth),
@@ -378,6 +412,7 @@ pub async fn payouts_list_available_filters_for_profile(
 ) -> HttpResponse {
     let flow = Flow::PayoutsFilter;
     let payload = json_payload.into_inner();
+    let locale = get_locale_from_header(req.headers());
 
     Box::pin(api::server_wrap(
         flow,
@@ -390,6 +425,7 @@ pub async fn payouts_list_available_filters_for_profile(
                 auth.merchant_account,
                 auth.profile_id.map(|profile_id| vec![profile_id]),
                 req,
+                &locale,
             )
         },
         auth::auth_type(
diff --git a/crates/router/src/workflows/attach_payout_account_workflow.rs b/crates/router/src/workflows/attach_payout_account_workflow.rs
index fa481fdb9d6..b29a15725b0 100644
--- a/crates/router/src/workflows/attach_payout_account_workflow.rs
+++ b/crates/router/src/workflows/attach_payout_account_workflow.rs
@@ -1,4 +1,7 @@
-use common_utils::ext_traits::{OptionExt, ValueExt};
+use common_utils::{
+    consts::DEFAULT_LOCALE,
+    ext_traits::{OptionExt, ValueExt},
+};
 use scheduler::{
     consumer::{self, workflows::ProcessTrackerWorkflow},
     errors,
@@ -46,8 +49,15 @@ impl ProcessTrackerWorkflow<SessionState> for AttachPayoutAccountWorkflow {
 
         let request = api::payouts::PayoutRequest::PayoutRetrieveRequest(tracking_data);
 
-        let mut payout_data =
-            payouts::make_payout_data(state, &merchant_account, None, &key_store, &request).await?;
+        let mut payout_data = payouts::make_payout_data(
+            state,
+            &merchant_account,
+            None,
+            &key_store,
+            &request,
+            DEFAULT_LOCALE,
+        )
+        .await?;
 
         payouts::payouts_core(
             state,
diff --git a/crates/router/src/workflows/outgoing_webhook_retry.rs b/crates/router/src/workflows/outgoing_webhook_retry.rs
index 138b1477d6a..e29277caf4e 100644
--- a/crates/router/src/workflows/outgoing_webhook_retry.rs
+++ b/crates/router/src/workflows/outgoing_webhook_retry.rs
@@ -5,7 +5,10 @@ use api_models::{
     webhook_events::OutgoingWebhookRequestContent,
     webhooks::{OutgoingWebhook, OutgoingWebhookContent},
 };
-use common_utils::ext_traits::{StringExt, ValueExt};
+use common_utils::{
+    consts::DEFAULT_LOCALE,
+    ext_traits::{StringExt, ValueExt},
+};
 use diesel_models::process_tracker::business_status;
 use error_stack::ResultExt;
 use masking::PeekInterface;
@@ -521,12 +524,18 @@ async fn get_outgoing_webhook_content_and_event_type(
                 payout_models::PayoutActionRequest { payout_id },
             );
 
-            let payout_data =
-                payouts::make_payout_data(&state, &merchant_account, None, &key_store, &request)
-                    .await?;
+            let payout_data = payouts::make_payout_data(
+                &state,
+                &merchant_account,
+                None,
+                &key_store,
+                &request,
+                DEFAULT_LOCALE,
+            )
+            .await?;
 
             let router_response =
-                payouts::response_handler(&merchant_account, &payout_data).await?;
+                payouts::response_handler(&state, &merchant_account, &payout_data).await?;
 
             let payout_create_response: payout_models::PayoutCreateResponse = match router_response
             {
diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs
index 697b5d91c93..e5bfe29af54 100644
--- a/crates/storage_impl/src/payouts/payout_attempt.rs
+++ b/crates/storage_impl/src/payouts/payout_attempt.rs
@@ -84,6 +84,8 @@ impl<T: DatabaseStore> PayoutAttemptInterface for KVRouterStore<T> {
                     profile_id: new_payout_attempt.profile_id.clone(),
                     merchant_connector_id: new_payout_attempt.merchant_connector_id.clone(),
                     routing_info: new_payout_attempt.routing_info.clone(),
+                    unified_code: new_payout_attempt.unified_code.clone(),
+                    unified_message: new_payout_attempt.unified_message.clone(),
                 };
 
                 let redis_entry = kv::TypedSql {
@@ -525,6 +527,8 @@ impl DataModelExt for PayoutAttempt {
             profile_id: self.profile_id,
             merchant_connector_id: self.merchant_connector_id,
             routing_info: self.routing_info,
+            unified_code: self.unified_code,
+            unified_message: self.unified_message,
         }
     }
 
@@ -549,6 +553,8 @@ impl DataModelExt for PayoutAttempt {
             profile_id: storage_model.profile_id,
             merchant_connector_id: storage_model.merchant_connector_id,
             routing_info: storage_model.routing_info,
+            unified_code: storage_model.unified_code,
+            unified_message: storage_model.unified_message,
         }
     }
 }
@@ -576,6 +582,8 @@ impl DataModelExt for PayoutAttemptNew {
             profile_id: self.profile_id,
             merchant_connector_id: self.merchant_connector_id,
             routing_info: self.routing_info,
+            unified_code: self.unified_code,
+            unified_message: self.unified_message,
         }
     }
 
@@ -600,6 +608,8 @@ impl DataModelExt for PayoutAttemptNew {
             profile_id: storage_model.profile_id,
             merchant_connector_id: storage_model.merchant_connector_id,
             routing_info: storage_model.routing_info,
+            unified_code: storage_model.unified_code,
+            unified_message: storage_model.unified_message,
         }
     }
 }
@@ -613,12 +623,16 @@ impl DataModelExt for PayoutAttemptUpdate {
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
             } => DieselPayoutAttemptUpdate::StatusUpdate {
                 connector_payout_id,
                 status,
                 error_message,
                 error_code,
                 is_eligible,
+                unified_code,
+                unified_message,
             },
             Self::PayoutTokenUpdate { payout_token } => {
                 DieselPayoutAttemptUpdate::PayoutTokenUpdate { payout_token }
diff --git a/migrations/2024-09-03-053218_add_unified_code_message_to_payout/down.sql b/migrations/2024-09-03-053218_add_unified_code_message_to_payout/down.sql
new file mode 100644
index 00000000000..b785cad78fb
--- /dev/null
+++ b/migrations/2024-09-03-053218_add_unified_code_message_to_payout/down.sql
@@ -0,0 +1,3 @@
+ALTER TABLE payout_attempt
+DROP COLUMN IF EXISTS unified_code,
+DROP COLUMN IF EXISTS unified_message;
\ No newline at end of file
diff --git a/migrations/2024-09-03-053218_add_unified_code_message_to_payout/up.sql b/migrations/2024-09-03-053218_add_unified_code_message_to_payout/up.sql
new file mode 100644
index 00000000000..b4ccfb8e525
--- /dev/null
+++ b/migrations/2024-09-03-053218_add_unified_code_message_to_payout/up.sql
@@ -0,0 +1,3 @@
+ALTER TABLE payout_attempt
+ADD COLUMN IF NOT EXISTS unified_code VARCHAR(255) DEFAULT NULL,
+ADD COLUMN IF NOT EXISTS unified_message VARCHAR(1024) DEFAULT NULL;
\ No newline at end of file

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

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

## Description
This adds `unified_code` and `unified_message` in the payout's API response. These values are looked up in the DB and populated in the API response.

`unified_code` and `unified_message` are then used to fetch the translated message string from DB (if present). This is finally returned in the payouts API response and / or embedded in the payout link.

### Additional Changes

- [x] This PR modifies the API contract
- [x] 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?
**Pre-requisites**

1. Ensure payout unified code and messages are inserted in `gateway_status_map` table
2. Ensure the translated messages for these unified code <> messages are inserted in `unified_translations` table

<details>
    <summary>1. Create an invalid payout txn</summary>

cURL

    curl --location 'http://localhost:8080/payouts/create' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_wP9pQWRBOq8PZ96NtlANhOHCevdADP7kWCgoQ6FvHQsjXNeL7JTVlCy7RiVW88t9' \
    --data '{
        "amount": 1,
        "currency": "EUR",
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "description": "Its my first payout request",
        "payout_type": "bank",
        "priority": "instant",
        "payout_method_data": {
            "bank": {
                "iban": "ES6614657716831375381753"
            }
        },
        "connector": [
            "adyenplatform"
        ],
        "billing": {
            "address": {
                "line1": "Raadhuisplein",
                "line2": "92",
                "city": "Hoogeveen",
                "state": "FL",
                "zip": "7901 BW",
                "country": "DE",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true,
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp"
    }'

Response with unified code and message

    {
        "payout_id": "d0ed3417-5f1f-46ad-8535-f02f2a8c96cf",
        "merchant_id": "merchant_1726461298",
        "amount": 1,
        "currency": "EUR",
        "connector": "adyenplatform",
        "payout_type": "bank",
        "billing": {
            "address": {
                "city": "Hoogeveen",
                "country": "DE",
                "line1": "Raadhuisplein",
                "line2": "92",
                "line3": null,
                "zip": "7901 BW",
                "state": "FL",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            },
            "email": null
        },
        "auto_fulfill": true,
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "customer": {
            "id": "cus_BF9z0OVpYLybZlkPLdeP",
            "name": "John Doe",
            "email": "guest@example.com",
            "phone": "6168205366",
            "phone_country_code": "+1"
        },
        "client_secret": "payout_d0ed3417-5f1f-46ad-8535-f02f2a8c96cf_secret_xlTRh9qeyr6Y5tb7flCn",
        "return_url": null,
        "business_country": null,
        "business_label": null,
        "description": "Its my first payout request",
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "merchant_connector_id": "mca_o9Pv2rZNURtL3mWcUGFN",
        "status": "failed",
        "error_message": "Invalid transfer information provided",
        "error_code": "30_081",
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp",
        "created": "2024-09-16T05:37:00.616Z",
        "connector_transaction_id": null,
        "priority": "instant",
        "payout_link": null,
        "email": "guest@example.com",
        "name": "John Doe",
        "phone": "6168205366",
        "phone_country_code": "+1",
        "unified_code": "UE_1200",
        "unified_message": "Invalid recipient details"
    }

</details>

<details>
    <summary>2. Create an invalid payout txn with language in the header</summary>

cURL

    curl --location 'http://localhost:8080/payouts/create' \
    --header 'Accept-Language: fr' \
    --header 'Content-Type: application/json' \
    --header 'api-key: dev_wP9pQWRBOq8PZ96NtlANhOHCevdADP7kWCgoQ6FvHQsjXNeL7JTVlCy7RiVW88t9' \
    --data '{
        "amount": 1,
        "currency": "EUR",
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "description": "Its my first payout request",
        "payout_type": "bank",
        "priority": "instant",
        "payout_method_data": {
            "bank": {
                "iban": "ES6614657716831375381753"
            }
        },
        "connector": [
            "adyenplatform"
        ],
        "billing": {
            "address": {
                "line1": "Raadhuisplein",
                "line2": "92",
                "city": "Hoogeveen",
                "state": "FL",
                "zip": "7901 BW",
                "country": "DE",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            }
        },
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "confirm": true,
        "auto_fulfill": true,
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp"
    }'

Response

    {
        "payout_id": "3933b577-8551-4a3b-b3b0-688a1001fe56",
        "merchant_id": "merchant_1726461298",
        "amount": 1,
        "currency": "EUR",
        "connector": "adyenplatform",
        "payout_type": "bank",
        "billing": {
            "address": {
                "city": "Hoogeveen",
                "country": "DE",
                "line1": "Raadhuisplein",
                "line2": "92",
                "line3": null,
                "zip": "7901 BW",
                "state": "FL",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            },
            "email": null
        },
        "auto_fulfill": true,
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "customer": {
            "id": "cus_BF9z0OVpYLybZlkPLdeP",
            "name": "John Doe",
            "email": "guest@example.com",
            "phone": "6168205366",
            "phone_country_code": "+1"
        },
        "client_secret": "payout_3933b577-8551-4a3b-b3b0-688a1001fe56_secret_Dtqie0KfQbmmBHdT1A8H",
        "return_url": null,
        "business_country": null,
        "business_label": null,
        "description": "Its my first payout request",
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "merchant_connector_id": "mca_o9Pv2rZNURtL3mWcUGFN",
        "status": "failed",
        "error_message": "Invalid transfer information provided",
        "error_code": "30_081",
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp",
        "created": "2024-09-16T05:43:24.401Z",
        "connector_transaction_id": null,
        "priority": "instant",
        "payout_link": null,
        "email": "guest@example.com",
        "name": "John Doe",
        "phone": "6168205366",
        "phone_country_code": "+1",
        "unified_code": "UE_1200",
        "unified_message": "Détails du destinataire invalides"
    }

</details>

<details>
    <summary>3. List this payout in different languages</summary>
    
cURL (ENGLISH)

    curl --location 'http://localhost:8080/payouts/3933b577-8551-4a3b-b3b0-688a1001fe56' \
    --header 'Accept-Language: en' \
    --header 'api-key: dev_wP9pQWRBOq8PZ96NtlANhOHCevdADP7kWCgoQ6FvHQsjXNeL7JTVlCy7RiVW88t9'

Response

    {
        "payout_id": "3933b577-8551-4a3b-b3b0-688a1001fe56",
        "merchant_id": "merchant_1726461298",
        "amount": 1,
        "currency": "EUR",
        "connector": "adyenplatform",
        "payout_type": "bank",
        "billing": {
            "address": {
                "city": "Hoogeveen",
                "country": "DE",
                "line1": "Raadhuisplein",
                "line2": "92",
                "line3": null,
                "zip": "7901 BW",
                "state": "FL",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            },
            "email": null
        },
        "auto_fulfill": true,
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "customer": {
            "id": "cus_BF9z0OVpYLybZlkPLdeP",
            "name": "John Doe",
            "email": "guest@example.com",
            "phone": "6168205366",
            "phone_country_code": "+1"
        },
        "client_secret": "payout_3933b577-8551-4a3b-b3b0-688a1001fe56_secret_Dtqie0KfQbmmBHdT1A8H",
        "return_url": null,
        "business_country": null,
        "business_label": null,
        "description": "Its my first payout request",
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "merchant_connector_id": "mca_o9Pv2rZNURtL3mWcUGFN",
        "status": "failed",
        "error_message": "Invalid transfer information provided",
        "error_code": "30_081",
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp",
        "created": "2024-09-16T05:43:24.401Z",
        "connector_transaction_id": null,
        "priority": "instant",
        "payout_link": null,
        "email": "guest@example.com",
        "name": "John Doe",
        "phone": "6168205366",
        "phone_country_code": "+1",
        "unified_code": "UE_1200",
        "unified_message": "Invalid recipient details"
    }

cURL (nl)

    curl --location 'http://localhost:8080/payouts/3933b577-8551-4a3b-b3b0-688a1001fe56' \
    --header 'Accept-Language: nl' \
    --header 'api-key: dev_wP9pQWRBOq8PZ96NtlANhOHCevdADP7kWCgoQ6FvHQsjXNeL7JTVlCy7RiVW88t9'

Response

    {
        "payout_id": "3933b577-8551-4a3b-b3b0-688a1001fe56",
        "merchant_id": "merchant_1726461298",
        "amount": 1,
        "currency": "EUR",
        "connector": "adyenplatform",
        "payout_type": "bank",
        "billing": {
            "address": {
                "city": "Hoogeveen",
                "country": "DE",
                "line1": "Raadhuisplein",
                "line2": "92",
                "line3": null,
                "zip": "7901 BW",
                "state": "FL",
                "first_name": "John",
                "last_name": "Doe"
            },
            "phone": {
                "number": "0650242319",
                "country_code": "+31"
            },
            "email": null
        },
        "auto_fulfill": true,
        "customer_id": "cus_BF9z0OVpYLybZlkPLdeP",
        "customer": {
            "id": "cus_BF9z0OVpYLybZlkPLdeP",
            "name": "John Doe",
            "email": "guest@example.com",
            "phone": "6168205366",
            "phone_country_code": "+1"
        },
        "client_secret": "payout_3933b577-8551-4a3b-b3b0-688a1001fe56_secret_Dtqie0KfQbmmBHdT1A8H",
        "return_url": null,
        "business_country": null,
        "business_label": null,
        "description": "Its my first payout request",
        "entity_type": "Individual",
        "recurring": true,
        "metadata": {
            "ref": "123"
        },
        "merchant_connector_id": "mca_o9Pv2rZNURtL3mWcUGFN",
        "status": "failed",
        "error_message": "Invalid transfer information provided",
        "error_code": "30_081",
        "profile_id": "pro_9GxLZ4gAqCxlLOnAndXp",
        "created": "2024-09-16T05:43:24.401Z",
        "connector_transaction_id": null,
        "priority": "instant",
        "payout_link": null,
        "email": "guest@example.com",
        "name": "John Doe",
        "phone": "6168205366",
        "phone_country_code": "+1",
        "unified_code": "UE_1200",
        "unified_message": "Ongeldige ontvangersgegevens"
    }

</details>

<details>
    <summary>4. Submit invalid details in payout details, and open status page</summary>

English

![image](https://github.com/user-attachments/assets/344fb32c-baaf-45e6-be1f-f3585bda428d)

French

![image](https://github.com/user-attachments/assets/c7a15de2-d4e6-44c1-984f-16132e51c4ea)

</details>



## 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
