mirror of
				https://github.com/juspay/hyperswitch.git
				synced 2025-10-31 01:57:45 +08:00 
			
		
		
		
	feat(core): add localization support for unified error messages (#5624)
Co-authored-by: Chikke Srujan <chikke.srujan@Chikke-Srujan-N7WRTY72X7.local> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com>
This commit is contained in:
		| @ -38,6 +38,7 @@ ws2ipdef = "ws2ipdef" # WinSock Extension | ||||
| ws2tcpip = "ws2tcpip" # WinSock Extension | ||||
| ZAR = "ZAR"  # South African Rand currency code | ||||
| JOD = "JOD" # Jordan currency code | ||||
| UE_000 = "UE_000" #default unified error code | ||||
|  | ||||
|  | ||||
| [default.extend-words] | ||||
|  | ||||
| @ -5435,6 +5435,8 @@ pub struct PaymentLinkStatusDetails { | ||||
|     pub return_url: String, | ||||
|     pub locale: Option<String>, | ||||
|     pub transaction_details: Option<String>, | ||||
|     pub unified_code: Option<String>, | ||||
|     pub unified_message: Option<String>, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] | ||||
|  | ||||
| @ -41,6 +41,7 @@ pub mod refund; | ||||
| pub mod reverse_lookup; | ||||
| pub mod role; | ||||
| pub mod routing_algorithm; | ||||
| pub mod unified_translations; | ||||
|  | ||||
| #[allow(unused_qualifications)] | ||||
| pub mod schema; | ||||
|  | ||||
| @ -36,6 +36,7 @@ pub mod refund; | ||||
| pub mod reverse_lookup; | ||||
| pub mod role; | ||||
| pub mod routing_algorithm; | ||||
| pub mod unified_translations; | ||||
| pub mod user; | ||||
| pub mod user_authentication_method; | ||||
| pub mod user_key_store; | ||||
|  | ||||
							
								
								
									
										79
									
								
								crates/diesel_models/src/query/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								crates/diesel_models/src/query/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,79 @@ | ||||
| use diesel::{associations::HasTable, BoolExpressionMethods, ExpressionMethods}; | ||||
| use error_stack::report; | ||||
|  | ||||
| use crate::{ | ||||
|     errors, | ||||
|     query::generics, | ||||
|     schema::unified_translations::dsl, | ||||
|     unified_translations::{UnifiedTranslationsUpdateInternal, *}, | ||||
|     PgPooledConn, StorageResult, | ||||
| }; | ||||
|  | ||||
| impl UnifiedTranslationsNew { | ||||
|     pub async fn insert(self, conn: &PgPooledConn) -> StorageResult<UnifiedTranslations> { | ||||
|         generics::generic_insert(conn, self).await | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl UnifiedTranslations { | ||||
|     pub async fn find_by_unified_code_unified_message_locale( | ||||
|         conn: &PgPooledConn, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> StorageResult<Self> { | ||||
|         generics::generic_find_one::<<Self as HasTable>::Table, _, _>( | ||||
|             conn, | ||||
|             dsl::unified_code | ||||
|                 .eq(unified_code) | ||||
|                 .and(dsl::unified_message.eq(unified_message)) | ||||
|                 .and(dsl::locale.eq(locale)), | ||||
|         ) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub async fn update_by_unified_code_unified_message_locale( | ||||
|         conn: &PgPooledConn, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|         data: UnifiedTranslationsUpdate, | ||||
|     ) -> StorageResult<Self> { | ||||
|         generics::generic_update_with_results::< | ||||
|             <Self as HasTable>::Table, | ||||
|             UnifiedTranslationsUpdateInternal, | ||||
|             _, | ||||
|             _, | ||||
|         >( | ||||
|             conn, | ||||
|             dsl::unified_code | ||||
|                 .eq(unified_code) | ||||
|                 .and(dsl::unified_message.eq(unified_message)) | ||||
|                 .and(dsl::locale.eq(locale)), | ||||
|             data.into(), | ||||
|         ) | ||||
|         .await? | ||||
|         .first() | ||||
|         .cloned() | ||||
|         .ok_or_else(|| { | ||||
|             report!(errors::DatabaseError::NotFound) | ||||
|                 .attach_printable("Error while updating unified_translations entry") | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     pub async fn delete_by_unified_code_unified_message_locale( | ||||
|         conn: &PgPooledConn, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> StorageResult<bool> { | ||||
|         generics::generic_delete::<<Self as HasTable>::Table, _>( | ||||
|             conn, | ||||
|             dsl::unified_code | ||||
|                 .eq(unified_code) | ||||
|                 .and(dsl::unified_message.eq(unified_message)) | ||||
|                 .and(dsl::locale.eq(locale)), | ||||
|         ) | ||||
|         .await | ||||
|     } | ||||
| } | ||||
| @ -1215,6 +1215,24 @@ diesel::table! { | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::table! { | ||||
|     use diesel::sql_types::*; | ||||
|     use crate::enums::diesel_exports::*; | ||||
|  | ||||
|     unified_translations (unified_code, unified_message, locale) { | ||||
|         #[max_length = 255] | ||||
|         unified_code -> Varchar, | ||||
|         #[max_length = 1024] | ||||
|         unified_message -> Varchar, | ||||
|         #[max_length = 255] | ||||
|         locale -> Varchar, | ||||
|         #[max_length = 1024] | ||||
|         translation -> Varchar, | ||||
|         created_at -> Timestamp, | ||||
|         last_modified_at -> Timestamp, | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::table! { | ||||
|     use diesel::sql_types::*; | ||||
|     use crate::enums::diesel_exports::*; | ||||
| @ -1343,6 +1361,7 @@ diesel::allow_tables_to_appear_in_same_query!( | ||||
|     reverse_lookup, | ||||
|     roles, | ||||
|     routing_algorithm, | ||||
|     unified_translations, | ||||
|     user_authentication_methods, | ||||
|     user_key_store, | ||||
|     user_roles, | ||||
|  | ||||
| @ -1197,6 +1197,24 @@ diesel::table! { | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::table! { | ||||
|     use diesel::sql_types::*; | ||||
|     use crate::enums::diesel_exports::*; | ||||
|  | ||||
|     unified_translations (unified_code, unified_message, locale) { | ||||
|         #[max_length = 255] | ||||
|         unified_code -> Varchar, | ||||
|         #[max_length = 1024] | ||||
|         unified_message -> Varchar, | ||||
|         #[max_length = 255] | ||||
|         locale -> Varchar, | ||||
|         #[max_length = 1024] | ||||
|         translation -> Varchar, | ||||
|         created_at -> Timestamp, | ||||
|         last_modified_at -> Timestamp, | ||||
|     } | ||||
| } | ||||
|  | ||||
| diesel::table! { | ||||
|     use diesel::sql_types::*; | ||||
|     use crate::enums::diesel_exports::*; | ||||
| @ -1326,6 +1344,7 @@ diesel::allow_tables_to_appear_in_same_query!( | ||||
|     reverse_lookup, | ||||
|     roles, | ||||
|     routing_algorithm, | ||||
|     unified_translations, | ||||
|     user_authentication_methods, | ||||
|     user_key_store, | ||||
|     user_roles, | ||||
|  | ||||
							
								
								
									
										50
									
								
								crates/diesel_models/src/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								crates/diesel_models/src/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,50 @@ | ||||
| //! Translations | ||||
|  | ||||
| use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; | ||||
| use time::PrimitiveDateTime; | ||||
|  | ||||
| use crate::schema::unified_translations; | ||||
|  | ||||
| #[derive(Clone, Debug, Queryable, Selectable, Identifiable)] | ||||
| #[diesel(table_name = unified_translations, primary_key(unified_code, unified_message, locale), check_for_backend(diesel::pg::Pg))] | ||||
| pub struct UnifiedTranslations { | ||||
|     pub unified_code: String, | ||||
|     pub unified_message: String, | ||||
|     pub locale: String, | ||||
|     pub translation: String, | ||||
|     pub created_at: PrimitiveDateTime, | ||||
|     pub last_modified_at: PrimitiveDateTime, | ||||
| } | ||||
| #[derive(Clone, Debug, Insertable)] | ||||
| #[diesel(table_name = unified_translations)] | ||||
| pub struct UnifiedTranslationsNew { | ||||
|     pub unified_code: String, | ||||
|     pub unified_message: String, | ||||
|     pub locale: String, | ||||
|     pub translation: String, | ||||
|     pub created_at: PrimitiveDateTime, | ||||
|     pub last_modified_at: PrimitiveDateTime, | ||||
| } | ||||
|  | ||||
| #[derive(Clone, Debug, AsChangeset)] | ||||
| #[diesel(table_name = unified_translations)] | ||||
| pub struct UnifiedTranslationsUpdateInternal { | ||||
|     pub translation: Option<String>, | ||||
|     pub last_modified_at: PrimitiveDateTime, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub struct UnifiedTranslationsUpdate { | ||||
|     pub translation: Option<String>, | ||||
| } | ||||
|  | ||||
| impl From<UnifiedTranslationsUpdate> for UnifiedTranslationsUpdateInternal { | ||||
|     fn from(value: UnifiedTranslationsUpdate) -> Self { | ||||
|         let now = common_utils::date_time::now(); | ||||
|         let UnifiedTranslationsUpdate { translation } = value; | ||||
|         Self { | ||||
|             translation, | ||||
|             last_modified_at: now, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -130,3 +130,7 @@ pub const CONNECTOR_CREDS_TOKEN_TTL: i64 = 900; | ||||
|  | ||||
| //max_amount allowed is 999999999 in minor units | ||||
| pub const MAX_ALLOWED_AMOUNT: i64 = 999999999; | ||||
|  | ||||
| //payment attempt default unified error code and unified error message | ||||
| pub const DEFAULT_UNIFIED_ERROR_CODE: &str = "UE_000"; | ||||
| pub const DEFAULT_UNIFIED_ERROR_MESSAGE: &str = "Something went wrong"; | ||||
|  | ||||
| @ -11,7 +11,7 @@ use common_utils::{ | ||||
|         DEFAULT_PRODUCT_IMG, DEFAULT_SDK_LAYOUT, DEFAULT_SESSION_EXPIRY, | ||||
|         DEFAULT_TRANSACTION_DETAILS, | ||||
|     }, | ||||
|     ext_traits::{OptionExt, ValueExt}, | ||||
|     ext_traits::{AsyncExt, OptionExt, ValueExt}, | ||||
|     types::{AmountConvertor, MinorUnit, StringMajorUnitForCore}, | ||||
| }; | ||||
| use error_stack::{report, ResultExt}; | ||||
| @ -21,8 +21,12 @@ use masking::{PeekInterface, Secret}; | ||||
| use router_env::logger; | ||||
| use time::PrimitiveDateTime; | ||||
|  | ||||
| use super::errors::{self, RouterResult, StorageErrorExt}; | ||||
| use super::{ | ||||
|     errors::{self, RouterResult, StorageErrorExt}, | ||||
|     payments::helpers, | ||||
| }; | ||||
| use crate::{ | ||||
|     consts, | ||||
|     errors::RouterResponse, | ||||
|     get_payment_link_config_value, get_payment_link_config_value_based_on_priority, | ||||
|     headers::ACCEPT_LANGUAGE, | ||||
| @ -222,6 +226,8 @@ pub async fn form_payment_link_data( | ||||
|             return_url: return_url.clone(), | ||||
|             locale: locale.clone(), | ||||
|             transaction_details: payment_link_config.transaction_details.clone(), | ||||
|             unified_code: payment_attempt.unified_code, | ||||
|             unified_message: payment_attempt.unified_message, | ||||
|         }; | ||||
|  | ||||
|         return Ok(( | ||||
| @ -760,6 +766,31 @@ pub async fn get_payment_link_status( | ||||
|                 field_name: "return_url", | ||||
|             })? | ||||
|     }; | ||||
|     let (unified_code, unified_message) = if let Some((code, message)) = payment_attempt | ||||
|         .unified_code | ||||
|         .as_ref() | ||||
|         .zip(payment_attempt.unified_message.as_ref()) | ||||
|     { | ||||
|         (code.to_owned(), message.to_owned()) | ||||
|     } else { | ||||
|         ( | ||||
|             consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(), | ||||
|             consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), | ||||
|         ) | ||||
|     }; | ||||
|     let unified_translated_message = locale | ||||
|         .as_ref() | ||||
|         .async_and_then(|locale_str| async { | ||||
|             helpers::get_unified_translation( | ||||
|                 &state, | ||||
|                 unified_code.to_owned(), | ||||
|                 unified_message.to_owned(), | ||||
|                 locale_str.to_owned(), | ||||
|             ) | ||||
|             .await | ||||
|         }) | ||||
|         .await | ||||
|         .or(Some(unified_message)); | ||||
|  | ||||
|     let payment_details = api_models::payments::PaymentLinkStatusDetails { | ||||
|         amount, | ||||
| @ -776,6 +807,8 @@ pub async fn get_payment_link_status( | ||||
|         return_url, | ||||
|         locale, | ||||
|         transaction_details: payment_link_config.transaction_details, | ||||
|         unified_code: Some(unified_code), | ||||
|         unified_message: unified_translated_message, | ||||
|     }; | ||||
|     let js_script = get_js_script(&PaymentLinkData::PaymentLinkStatusDetails(Box::new( | ||||
|         payment_details, | ||||
|  | ||||
| @ -167,10 +167,12 @@ function renderStatusDetails(paymentDetails) { | ||||
|     case "failed": | ||||
|       statusDetails.imageSource = "https://live.hyperswitch.io/payment-link-assets/failed.png"; | ||||
|       statusDetails.status = translations.paymentFailed; | ||||
|       var errorCodeNode = createItem(translations.errorCode, paymentDetails.error_code); | ||||
|       var unifiedErrorCode = paymentDetails.unified_code || paymentDetails.error_code; | ||||
|       var unifiedErrorMessage = paymentDetails.unified_message || paymentDetails.error_message; | ||||
|       var errorCodeNode = createItem(translations.errorCode, unifiedErrorCode); | ||||
|       var errorMessageNode = createItem( | ||||
|         translations.errorMessage, | ||||
|         paymentDetails.error_message | ||||
|         unifiedErrorMessage | ||||
|       ); | ||||
|       // @ts-ignore | ||||
|       statusDetails.items.push(errorMessageNode, errorCodeNode); | ||||
|  | ||||
| @ -207,6 +207,8 @@ where | ||||
|  | ||||
|     let should_add_task_to_process_tracker = should_add_task_to_process_tracker(&payment_data); | ||||
|  | ||||
|     let locale = header_payload.locale.clone(); | ||||
|  | ||||
|     payment_data = tokenize_in_router_when_confirm_false_or_external_authentication( | ||||
|         state, | ||||
|         &operation, | ||||
| @ -352,6 +354,7 @@ where | ||||
|                             router_data, | ||||
|                             &key_store, | ||||
|                             merchant_account.storage_scheme, | ||||
|                             &locale, | ||||
|                         ) | ||||
|                         .await?; | ||||
|  | ||||
| @ -475,6 +478,7 @@ where | ||||
|                             router_data, | ||||
|                             &key_store, | ||||
|                             merchant_account.storage_scheme, | ||||
|                             &locale, | ||||
|                         ) | ||||
|                         .await?; | ||||
|  | ||||
|  | ||||
| @ -4701,6 +4701,40 @@ pub async fn get_gsm_record( | ||||
|         .ok() | ||||
| } | ||||
|  | ||||
| pub async fn get_unified_translation( | ||||
|     state: &SessionState, | ||||
|     unified_code: String, | ||||
|     unified_message: String, | ||||
|     locale: String, | ||||
| ) -> Option<String> { | ||||
|     let get_unified_translation = || async { | ||||
|         state.store.find_translation( | ||||
|                 unified_code.clone(), | ||||
|                 unified_message.clone(), | ||||
|                 locale.clone(), | ||||
|             ) | ||||
|             .await | ||||
|             .map_err(|err| { | ||||
|                 if err.current_context().is_db_not_found() { | ||||
|                     logger::warn!( | ||||
|                         "Translation missing for unified_code - {:?}, unified_message - {:?}, locale - {:?}", | ||||
|                         unified_code, | ||||
|                         unified_message, | ||||
|                         locale | ||||
|                     ); | ||||
|                 } | ||||
|                 err.change_context(errors::ApiErrorResponse::InternalServerError) | ||||
|                     .attach_printable("failed to fetch translation from unified_translations") | ||||
|             }) | ||||
|     }; | ||||
|     get_unified_translation() | ||||
|         .await | ||||
|         .inspect_err(|err| { | ||||
|             // warn log should suffice here because we are not propagating this error | ||||
|             logger::warn!(get_translation_error=?err, "error fetching unified translations"); | ||||
|         }) | ||||
|         .ok() | ||||
| } | ||||
| pub fn validate_order_details_amount( | ||||
|     order_details: Vec<api_models::payments::OrderDetailsWithAmount>, | ||||
|     amount: i64, | ||||
|  | ||||
| @ -220,6 +220,7 @@ pub trait UpdateTracker<F, D, Req>: Send { | ||||
| } | ||||
|  | ||||
| #[async_trait] | ||||
| #[allow(clippy::too_many_arguments)] | ||||
| pub trait PostUpdateTracker<F, D, R: Send>: Send { | ||||
|     async fn update_tracker<'b>( | ||||
|         &'b self, | ||||
| @ -229,6 +230,7 @@ pub trait PostUpdateTracker<F, D, R: Send>: Send { | ||||
|         response: types::RouterData<F, R, PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<D> | ||||
|     where | ||||
|         F: 'b + Send + Sync; | ||||
|  | ||||
| @ -3,7 +3,7 @@ use std::collections::HashMap; | ||||
| use async_trait::async_trait; | ||||
| use common_enums::AuthorizationStatus; | ||||
| use common_utils::{ | ||||
|     ext_traits::Encode, | ||||
|     ext_traits::{AsyncExt, Encode}, | ||||
|     types::{keymanager::KeyManagerState, MinorUnit}, | ||||
| }; | ||||
| use error_stack::{report, ResultExt}; | ||||
| @ -17,6 +17,7 @@ use tracing_futures::Instrument; | ||||
| use super::{Operation, PostUpdateTracker}; | ||||
| use crate::{ | ||||
|     connector::utils::PaymentResponseRouterData, | ||||
|     consts, | ||||
|     core::{ | ||||
|         errors::{self, CustomResult, RouterResult, StorageErrorExt}, | ||||
|         mandate, payment_methods, | ||||
| @ -64,6 +65,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor | ||||
|         >, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b, | ||||
| @ -79,6 +81,7 @@ impl<F: Send + Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsAuthor | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -278,6 +281,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsIncrementalAu | ||||
|         >, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         _locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -409,6 +413,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for | ||||
|         router_data: types::RouterData<F, types::PaymentsSyncData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -420,6 +425,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSyncData> for | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await | ||||
|     } | ||||
| @ -461,6 +467,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData> | ||||
|         router_data: types::RouterData<F, types::PaymentsSessionData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -472,6 +479,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsSessionData> | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -491,6 +499,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData> | ||||
|         router_data: types::RouterData<F, types::PaymentsCaptureData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -502,6 +511,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCaptureData> | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -519,6 +529,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f | ||||
|         router_data: types::RouterData<F, types::PaymentsCancelData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -530,6 +541,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsCancelData> f | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -549,6 +561,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsApproveData> | ||||
|         router_data: types::RouterData<F, types::PaymentsApproveData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -560,6 +573,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsApproveData> | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -577,6 +591,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsRejectData> f | ||||
|         router_data: types::RouterData<F, types::PaymentsRejectData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -588,6 +603,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::PaymentsRejectData> f | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -611,6 +627,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa | ||||
|         >, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -627,6 +644,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::SetupMandateRequestDa | ||||
|             router_data, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await?; | ||||
|  | ||||
| @ -709,6 +727,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::CompleteAuthorizeData | ||||
|         response: types::RouterData<F, types::CompleteAuthorizeData, types::PaymentsResponseData>, | ||||
|         key_store: &domain::MerchantKeyStore, | ||||
|         storage_scheme: enums::MerchantStorageScheme, | ||||
|         locale: &Option<String>, | ||||
|     ) -> RouterResult<PaymentData<F>> | ||||
|     where | ||||
|         F: 'b + Send, | ||||
| @ -720,6 +739,7 @@ impl<F: Clone> PostUpdateTracker<F, PaymentData<F>, types::CompleteAuthorizeData | ||||
|             response, | ||||
|             key_store, | ||||
|             storage_scheme, | ||||
|             locale, | ||||
|         )) | ||||
|         .await | ||||
|     } | ||||
| @ -757,6 +777,7 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|     router_data: types::RouterData<F, T, types::PaymentsResponseData>, | ||||
|     key_store: &domain::MerchantKeyStore, | ||||
|     storage_scheme: enums::MerchantStorageScheme, | ||||
|     locale: &Option<String>, | ||||
| ) -> RouterResult<PaymentData<F>> { | ||||
|     // Update additional payment data with the payment method response that we received from connector | ||||
|     let additional_payment_method_data = | ||||
| @ -822,6 +843,34 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|                     ) | ||||
|                     .await; | ||||
|  | ||||
|                     let gsm_unified_code = | ||||
|                         option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); | ||||
|                     let gsm_unified_message = option_gsm.and_then(|gsm| gsm.unified_message); | ||||
|  | ||||
|                     let (unified_code, unified_message) = if let Some((code, message)) = | ||||
|                         gsm_unified_code.as_ref().zip(gsm_unified_message.as_ref()) | ||||
|                     { | ||||
|                         (code.to_owned(), message.to_owned()) | ||||
|                     } else { | ||||
|                         ( | ||||
|                             consts::DEFAULT_UNIFIED_ERROR_CODE.to_owned(), | ||||
|                             consts::DEFAULT_UNIFIED_ERROR_MESSAGE.to_owned(), | ||||
|                         ) | ||||
|                     }; | ||||
|                     let unified_translated_message = locale | ||||
|                         .as_ref() | ||||
|                         .async_and_then(|locale_str| async { | ||||
|                             payments_helpers::get_unified_translation( | ||||
|                                 state, | ||||
|                                 unified_code.to_owned(), | ||||
|                                 unified_message.to_owned(), | ||||
|                                 locale_str.to_owned(), | ||||
|                             ) | ||||
|                             .await | ||||
|                         }) | ||||
|                         .await | ||||
|                         .or(Some(unified_message)); | ||||
|  | ||||
|                     let status = match err.attempt_status { | ||||
|                         // Use the status sent by connector in error_response if it's present | ||||
|                         Some(status) => status, | ||||
| @ -862,8 +911,8 @@ async fn payment_response_update_tracker<F: Clone, T: types::Capturable>( | ||||
|                                 .get_amount_capturable(&payment_data, status) | ||||
|                                 .map(MinorUnit::new), | ||||
|                             updated_by: storage_scheme.to_string(), | ||||
|                             unified_code: option_gsm.clone().map(|gsm| gsm.unified_code), | ||||
|                             unified_message: option_gsm.map(|gsm| gsm.unified_message), | ||||
|                             unified_code: Some(Some(unified_code)), | ||||
|                             unified_message: Some(unified_translated_message), | ||||
|                             connector_transaction_id: err.connector_transaction_id, | ||||
|                             payment_method_data: additional_payment_method_data, | ||||
|                             authentication_type: auth_update, | ||||
|  | ||||
| @ -32,6 +32,7 @@ pub mod refund; | ||||
| pub mod reverse_lookup; | ||||
| pub mod role; | ||||
| pub mod routing_algorithm; | ||||
| pub mod unified_translations; | ||||
| pub mod user; | ||||
| pub mod user_authentication_method; | ||||
| pub mod user_key_store; | ||||
| @ -121,6 +122,7 @@ pub trait StorageInterface: | ||||
|     + OrganizationInterface | ||||
|     + routing_algorithm::RoutingAlgorithmInterface | ||||
|     + gsm::GsmInterface | ||||
|     + unified_translations::UnifiedTranslationsInterface | ||||
|     + user_role::UserRoleInterface | ||||
|     + authorization::AuthorizationInterface | ||||
|     + user::sample_data::BatchSampleDataInterface | ||||
|  | ||||
| @ -68,6 +68,7 @@ use crate::{ | ||||
|         refund::RefundInterface, | ||||
|         reverse_lookup::ReverseLookupInterface, | ||||
|         routing_algorithm::RoutingAlgorithmInterface, | ||||
|         unified_translations::UnifiedTranslationsInterface, | ||||
|         CommonStorageInterface, GlobalStorageInterface, MasterKeyInterface, StorageInterface, | ||||
|     }, | ||||
|     services::{authentication, kafka::KafkaProducer, Store}, | ||||
| @ -2613,6 +2614,50 @@ impl GsmInterface for KafkaStore { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl UnifiedTranslationsInterface for KafkaStore { | ||||
|     async fn add_unfied_translation( | ||||
|         &self, | ||||
|         translation: storage::UnifiedTranslationsNew, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         self.diesel_store.add_unfied_translation(translation).await | ||||
|     } | ||||
|  | ||||
|     async fn find_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<String, errors::StorageError> { | ||||
|         self.diesel_store | ||||
|             .find_translation(unified_code, unified_message, locale) | ||||
|             .await | ||||
|     } | ||||
|  | ||||
|     async fn update_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|         data: storage::UnifiedTranslationsUpdate, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         self.diesel_store | ||||
|             .update_translation(unified_code, unified_message, locale, data) | ||||
|             .await | ||||
|     } | ||||
|  | ||||
|     async fn delete_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<bool, errors::StorageError> { | ||||
|         self.diesel_store | ||||
|             .delete_translation(unified_code, unified_message, locale) | ||||
|             .await | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl StorageInterface for KafkaStore { | ||||
|     fn get_scheduler_db(&self) -> Box<dyn SchedulerInterface> { | ||||
|  | ||||
							
								
								
									
										146
									
								
								crates/router/src/db/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										146
									
								
								crates/router/src/db/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,146 @@ | ||||
| use diesel_models::unified_translations as storage; | ||||
| use error_stack::report; | ||||
|  | ||||
| use super::MockDb; | ||||
| use crate::{ | ||||
|     connection, | ||||
|     core::errors::{self, CustomResult}, | ||||
|     services::Store, | ||||
| }; | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| pub trait UnifiedTranslationsInterface { | ||||
|     async fn add_unfied_translation( | ||||
|         &self, | ||||
|         translation: storage::UnifiedTranslationsNew, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError>; | ||||
|  | ||||
|     async fn update_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|         data: storage::UnifiedTranslationsUpdate, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError>; | ||||
|  | ||||
|     async fn find_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<String, errors::StorageError>; | ||||
|  | ||||
|     async fn delete_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<bool, errors::StorageError>; | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl UnifiedTranslationsInterface for Store { | ||||
|     async fn add_unfied_translation( | ||||
|         &self, | ||||
|         translation: storage::UnifiedTranslationsNew, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         let conn = connection::pg_connection_write(self).await?; | ||||
|         translation | ||||
|             .insert(&conn) | ||||
|             .await | ||||
|             .map_err(|error| report!(errors::StorageError::from(error))) | ||||
|     } | ||||
|  | ||||
|     async fn update_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|         data: storage::UnifiedTranslationsUpdate, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         let conn = connection::pg_connection_write(self).await?; | ||||
|         storage::UnifiedTranslations::update_by_unified_code_unified_message_locale( | ||||
|             &conn, | ||||
|             unified_code, | ||||
|             unified_message, | ||||
|             locale, | ||||
|             data, | ||||
|         ) | ||||
|         .await | ||||
|         .map_err(|error| report!(errors::StorageError::from(error))) | ||||
|     } | ||||
|  | ||||
|     async fn find_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<String, errors::StorageError> { | ||||
|         let conn = connection::pg_connection_read(self).await?; | ||||
|         let translations = | ||||
|             storage::UnifiedTranslations::find_by_unified_code_unified_message_locale( | ||||
|                 &conn, | ||||
|                 unified_code, | ||||
|                 unified_message, | ||||
|                 locale, | ||||
|             ) | ||||
|             .await | ||||
|             .map_err(|error| report!(errors::StorageError::from(error)))?; | ||||
|         Ok(translations.translation) | ||||
|     } | ||||
|  | ||||
|     async fn delete_translation( | ||||
|         &self, | ||||
|         unified_code: String, | ||||
|         unified_message: String, | ||||
|         locale: String, | ||||
|     ) -> CustomResult<bool, errors::StorageError> { | ||||
|         let conn = connection::pg_connection_write(self).await?; | ||||
|         storage::UnifiedTranslations::delete_by_unified_code_unified_message_locale( | ||||
|             &conn, | ||||
|             unified_code, | ||||
|             unified_message, | ||||
|             locale, | ||||
|         ) | ||||
|         .await | ||||
|         .map_err(|error| report!(errors::StorageError::from(error))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[async_trait::async_trait] | ||||
| impl UnifiedTranslationsInterface for MockDb { | ||||
|     async fn add_unfied_translation( | ||||
|         &self, | ||||
|         _translation: storage::UnifiedTranslationsNew, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         Err(errors::StorageError::MockDbError)? | ||||
|     } | ||||
|  | ||||
|     async fn find_translation( | ||||
|         &self, | ||||
|         _unified_code: String, | ||||
|         _unified_message: String, | ||||
|         _locale: String, | ||||
|     ) -> CustomResult<String, errors::StorageError> { | ||||
|         Err(errors::StorageError::MockDbError)? | ||||
|     } | ||||
|  | ||||
|     async fn update_translation( | ||||
|         &self, | ||||
|         _unified_code: String, | ||||
|         _unified_message: String, | ||||
|         _locale: String, | ||||
|         _data: storage::UnifiedTranslationsUpdate, | ||||
|     ) -> CustomResult<storage::UnifiedTranslations, errors::StorageError> { | ||||
|         Err(errors::StorageError::MockDbError)? | ||||
|     } | ||||
|  | ||||
|     async fn delete_translation( | ||||
|         &self, | ||||
|         _unified_code: String, | ||||
|         _unified_message: String, | ||||
|         _locale: String, | ||||
|     ) -> CustomResult<bool, errors::StorageError> { | ||||
|         Err(errors::StorageError::MockDbError)? | ||||
|     } | ||||
| } | ||||
| @ -262,6 +262,12 @@ pub async fn payments_retrieve( | ||||
|         expand_captures: json_payload.expand_captures, | ||||
|         ..Default::default() | ||||
|     }; | ||||
|     let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { | ||||
|         Ok(headers) => headers, | ||||
|         Err(err) => { | ||||
|             return api::log_and_return_error_response(err); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     tracing::Span::current().record("payment_id", path.to_string()); | ||||
|     tracing::Span::current().record("flow", flow.to_string()); | ||||
| @ -291,7 +297,7 @@ pub async fn payments_retrieve( | ||||
|                 auth_flow, | ||||
|                 payments::CallConnectorAction::Trigger, | ||||
|                 None, | ||||
|                 HeaderPayload::default(), | ||||
|                 header_payload.clone(), | ||||
|             ) | ||||
|         }, | ||||
|         auth::auth_type( | ||||
|  | ||||
| @ -35,6 +35,7 @@ pub mod refund; | ||||
| pub mod reverse_lookup; | ||||
| pub mod role; | ||||
| pub mod routing_algorithm; | ||||
| pub mod unified_translations; | ||||
| pub mod user; | ||||
| pub mod user_authentication_method; | ||||
| pub mod user_role; | ||||
| @ -64,7 +65,8 @@ pub use self::{ | ||||
|     file::*, fraud_check::*, generic_link::*, gsm::*, locker_mock_up::*, mandate::*, | ||||
|     merchant_account::*, merchant_connector_account::*, merchant_key_store::*, payment_link::*, | ||||
|     payment_method::*, process_tracker::*, refund::*, reverse_lookup::*, role::*, | ||||
|     routing_algorithm::*, user::*, user_authentication_method::*, user_role::*, | ||||
|     routing_algorithm::*, unified_translations::*, user::*, user_authentication_method::*, | ||||
|     user_role::*, | ||||
| }; | ||||
| use crate::types::api::routing; | ||||
|  | ||||
|  | ||||
							
								
								
									
										4
									
								
								crates/router/src/types/storage/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								crates/router/src/types/storage/unified_translations.rs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,4 @@ | ||||
| pub use diesel_models::unified_translations::{ | ||||
|     UnifiedTranslations, UnifiedTranslationsNew, UnifiedTranslationsUpdate, | ||||
|     UnifiedTranslationsUpdateInternal, | ||||
| }; | ||||
| @ -0,0 +1,2 @@ | ||||
| -- This file should undo anything in `up.sql` | ||||
| DROP TABLE IF EXISTS unified_translations; | ||||
| @ -0,0 +1,9 @@ | ||||
| CREATE TABLE IF NOT EXISTS unified_translations ( | ||||
|     unified_code VARCHAR(255) NOT NULL, | ||||
|     unified_message VARCHAR(1024) NOT NULL, | ||||
|     locale VARCHAR(255) NOT NULL , | ||||
|     translation VARCHAR(1024) NOT NULL, | ||||
|     created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, | ||||
|     last_modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, | ||||
|     PRIMARY KEY (unified_code,unified_message,locale) | ||||
| ); | ||||
		Reference in New Issue
	
	Block a user
	 chikke srujan
					chikke srujan