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:
chikke srujan
2024-08-21 18:05:44 +05:30
committed by GitHub
parent 1d08c7b932
commit 1f0ee3cae0
23 changed files with 525 additions and 9 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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?;

View File

@ -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,

View File

@ -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;

View File

@ -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,