mirror of
https://github.com/juspay/hyperswitch.git
synced 2025-11-02 04:04:43 +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:
@ -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,
|
||||
|
||||
Reference in New Issue
Block a user