diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 3c80fd8c4634..5167df152a07 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -23239,6 +23239,16 @@ "description": "The code for the error", "nullable": true }, + "unified_code": { + "type": "string", + "description": "Error code unified across the connectors is received here if there was an error while calling connector", + "nullable": true + }, + "unified_message": { + "type": "string", + "description": "Error message unified across the connectors is received here if there was an error while calling connector", + "nullable": true + }, "created_at": { "type": "string", "format": "date-time", diff --git a/crates/api_models/src/refunds.rs b/crates/api_models/src/refunds.rs index 36a92e3ab03d..ad09c333ea71 100644 --- a/crates/api_models/src/refunds.rs +++ b/crates/api_models/src/refunds.rs @@ -188,6 +188,10 @@ pub struct RefundResponse { pub error_message: Option, /// The code for the error pub error_code: Option, + /// Error code unified across the connectors is received here if there was an error while calling connector + pub unified_code: Option, + /// Error message unified across the connectors is received here if there was an error while calling connector + pub unified_message: Option, /// The timestamp at which refund is created #[serde(with = "common_utils::custom_serde::iso8601::option")] pub created_at: Option, diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index 758eba7255ae..b929481d64d5 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -54,6 +54,8 @@ pub struct Refund { pub connector_refund_data: Option, pub connector_transaction_data: Option, pub split_refunds: Option, + pub unified_code: Option, + pub unified_message: Option, } #[derive( @@ -132,6 +134,8 @@ pub enum RefundUpdate { updated_by: String, connector_refund_id: Option, connector_refund_data: Option, + unified_code: Option, + unified_message: Option, }, ManualUpdate { refund_status: Option, @@ -155,6 +159,8 @@ pub struct RefundUpdateInternal { updated_by: String, modified_at: PrimitiveDateTime, connector_refund_data: Option, + unified_code: Option, + unified_message: Option, } impl RefundUpdateInternal { @@ -171,6 +177,8 @@ impl RefundUpdateInternal { updated_by: self.updated_by, modified_at: self.modified_at, connector_refund_data: self.connector_refund_data, + unified_code: self.unified_code, + unified_message: self.unified_message, ..source } } @@ -199,6 +207,8 @@ impl From for RefundUpdateInternal { refund_reason: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + unified_code: None, + unified_message: None, }, RefundUpdate::MetadataAndReasonUpdate { metadata, @@ -216,6 +226,8 @@ impl From for RefundUpdateInternal { refund_error_code: None, modified_at: common_utils::date_time::now(), connector_refund_data: None, + unified_code: None, + unified_message: None, }, RefundUpdate::StatusUpdate { connector_refund_id, @@ -235,11 +247,15 @@ impl From for RefundUpdateInternal { refund_reason: None, refund_error_code: None, modified_at: common_utils::date_time::now(), + unified_code: None, + unified_message: None, }, RefundUpdate::ErrorUpdate { refund_status, refund_error_message, refund_error_code, + unified_code, + unified_message, updated_by, connector_refund_id, connector_refund_data, @@ -255,6 +271,8 @@ impl From for RefundUpdateInternal { metadata: None, refund_reason: None, modified_at: common_utils::date_time::now(), + unified_code, + unified_message, }, RefundUpdate::ManualUpdate { refund_status, @@ -273,6 +291,8 @@ impl From for RefundUpdateInternal { refund_reason: None, modified_at: common_utils::date_time::now(), connector_refund_data: None, + unified_code: None, + unified_message: None, }, } } @@ -292,6 +312,8 @@ impl RefundUpdate { updated_by, modified_at: _, connector_refund_data, + unified_code, + unified_message, } = self.into(); Refund { connector_refund_id: connector_refund_id.or(source.connector_refund_id), @@ -305,6 +327,8 @@ impl RefundUpdate { updated_by, modified_at: common_utils::date_time::now(), connector_refund_data: connector_refund_data.or(source.connector_refund_data), + unified_code: unified_code.or(source.unified_code), + unified_message: unified_message.or(source.unified_message), ..source } } @@ -392,6 +416,8 @@ mod tests { "merchant_connector_id": null, "charges": null, "connector_transaction_data": null + "unified_code": null, + "unified_message": null, }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 95bb714cb711..2de6246a44cc 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -1235,6 +1235,10 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, split_refunds -> Nullable, + #[max_length = 255] + unified_code -> Nullable, + #[max_length = 1024] + unified_message -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 8bbb4baf9d74..896a9098e6f3 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -1181,6 +1181,10 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, split_refunds -> Nullable, + #[max_length = 255] + unified_code -> Nullable, + #[max_length = 1024] + unified_message -> Nullable, } } diff --git a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index 075a7b070d6d..45a7b577a86b 100644 --- a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -147,12 +147,6 @@ pub struct Browser { user_agent: String, } -#[derive(Debug, Serialize)] -pub struct Location { - lat: String, - lon: String, -} - #[derive(Debug, Serialize)] pub struct Mobile { device_model: Option, diff --git a/crates/router/src/bin/scheduler.rs b/crates/router/src/bin/scheduler.rs index 62c8c03e1a97..e18ed318be17 100644 --- a/crates/router/src/bin/scheduler.rs +++ b/crates/router/src/bin/scheduler.rs @@ -158,7 +158,7 @@ pub async fn deep_health_check( let app_state = Arc::clone(&state.into_inner()); let service_name = service.into_inner(); for (tenant, _) in stores { - let session_state_res = app_state.clone().get_session_state(&tenant, || { + let session_state_res = app_state.clone().get_session_state(&tenant, None, || { errors::ApiErrorResponse::MissingRequiredField { field_name: "tenant_id", } @@ -397,7 +397,7 @@ async fn start_scheduler( WorkflowRunner {}, |state, tenant| { Arc::new(state.clone()) - .get_session_state(tenant, || ProcessTrackerError::TenantNotFound.into()) + .get_session_state(tenant, None, || ProcessTrackerError::TenantNotFound.into()) }, ) .await diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index e2784b968ad1..bcc9a5918634 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -210,3 +210,6 @@ pub const DYNAMIC_ROUTING_MAX_VOLUME: u8 = 100; /// Click To Pay pub const CLICK_TO_PAY: &str = "click_to_pay"; + +/// Refund flow identifier used for performing GSM operations +pub const REFUND_FLOW_STR: &str = "refund_flow"; diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 498a00c240df..477803c92a27 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -6,7 +6,7 @@ use api_models::{ }; use common_utils::{ consts::{DEFAULT_LOCALE, DEFAULT_SESSION_EXPIRY}, - ext_traits::{AsyncExt, OptionExt, ValueExt}, + ext_traits::{OptionExt, ValueExt}, types::{AmountConvertor, StringMajorUnitForCore}, }; use error_stack::{report, ResultExt}; @@ -28,9 +28,8 @@ use crate::{ }, errors::RouterResponse, get_payment_link_config_value, get_payment_link_config_value_based_on_priority, - headers::ACCEPT_LANGUAGE, routes::SessionState, - services::{self, authentication::get_header_value_by_key}, + services, types::{ api::payment_link::PaymentLinkResponseExt, domain, @@ -70,7 +69,6 @@ pub async fn form_payment_link_data( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - locale: Option, ) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> { todo!() } @@ -82,7 +80,6 @@ pub async fn form_payment_link_data( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - locale: Option, ) -> RouterResult<(PaymentLink, PaymentLinkData, PaymentLinkConfig)> { let db = &*state.store; let key_manager_state = &state.into(); @@ -242,7 +239,7 @@ pub async fn form_payment_link_data( redirect: false, theme: payment_link_config.theme.clone(), return_url: return_url.clone(), - locale: locale.clone(), + locale: Some(state.clone().locale), transaction_details: payment_link_config.transaction_details.clone(), unified_code: payment_attempt.unified_code, unified_message: payment_attempt.unified_message, @@ -273,7 +270,7 @@ pub async fn form_payment_link_data( display_sdk_only: payment_link_config.display_sdk_only, hide_card_nickname_field: payment_link_config.hide_card_nickname_field, show_card_form_by_default: payment_link_config.show_card_form_by_default, - locale, + locale: Some(state.clone().locale), transaction_details: payment_link_config.transaction_details.clone(), background_image: payment_link_config.background_image.clone(), details_layout: payment_link_config.details_layout, @@ -296,17 +293,9 @@ pub async fn initiate_secure_payment_link_flow( payment_id: common_utils::id_type::PaymentId, request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); - let (payment_link, payment_link_details, payment_link_config) = form_payment_link_data( - &state, - merchant_account, - key_store, - merchant_id, - payment_id, - locale, - ) - .await?; + let (payment_link, payment_link_details, payment_link_config) = + form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id) + .await?; validator::validate_secure_payment_link_render_request( request_headers, @@ -396,19 +385,10 @@ pub async fn initiate_payment_link_flow( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); - let (_, payment_details, payment_link_config) = form_payment_link_data( - &state, - merchant_account, - key_store, - merchant_id, - payment_id, - locale, - ) - .await?; + let (_, payment_details, payment_link_config) = + form_payment_link_data(&state, merchant_account, key_store, merchant_id, payment_id) + .await?; let css_script = get_color_scheme_css(&payment_link_config); let js_script = get_js_script(&payment_details)?; @@ -727,7 +707,6 @@ pub async fn get_payment_link_status( _key_store: domain::MerchantKeyStore, _merchant_id: common_utils::id_type::MerchantId, _payment_id: common_utils::id_type::PaymentId, - _request_headers: &header::HeaderMap, ) -> RouterResponse { todo!() } @@ -739,10 +718,7 @@ pub async fn get_payment_link_status( key_store: domain::MerchantKeyStore, merchant_id: common_utils::id_type::MerchantId, payment_id: common_utils::id_type::PaymentId, - request_headers: &header::HeaderMap, ) -> RouterResponse { - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), request_headers)? - .map(|val| val.to_string()); let db = &*state.store; let key_manager_state = &(&state).into(); @@ -858,19 +834,14 @@ pub async fn get_payment_link_status( 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 unified_translated_message = helpers::get_unified_translation( + &state, + unified_code.to_owned(), + unified_message.to_owned(), + state.locale.clone(), + ) + .await + .or(Some(unified_message)); let payment_details = api_models::payments::PaymentLinkStatusDetails { amount, @@ -885,7 +856,7 @@ pub async fn get_payment_link_status( redirect: true, theme: payment_link_config.theme.clone(), return_url, - locale, + locale: Some(state.locale.clone()), transaction_details: payment_link_config.transaction_details, unified_code: Some(unified_code), unified_message: unified_translated_message, diff --git a/crates/router/src/core/payout_link.rs b/crates/router/src/core/payout_link.rs index 22354b4c00fc..e45b4fcead1d 100644 --- a/crates/router/src/core/payout_link.rs +++ b/crates/router/src/core/payout_link.rs @@ -46,7 +46,6 @@ pub async fn initiate_payout_link( key_store: domain::MerchantKeyStore, req: payouts::PayoutLinkInitiateRequest, request_headers: &header::HeaderMap, - locale: String, ) -> RouterResponse { let db: &dyn StorageInterface = &*state.store; let merchant_id = merchant_account.get_id(); @@ -128,7 +127,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::ExpiredLink(expired_link_data), - locale, + locale: state.locale, }, ))) } @@ -245,7 +244,7 @@ pub async fn initiate_payout_link( enabled_payment_methods_with_required_fields, amount, currency: payout.destination_currency, - locale: locale.clone(), + locale: state.locale.clone(), form_layout: link_data.form_layout, test_mode: link_data.test_mode.unwrap_or(false), }; @@ -270,7 +269,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::PayoutLink(generic_form_data), - locale, + locale: state.locale.clone(), }, ))) } @@ -282,7 +281,7 @@ pub async fn initiate_payout_link( &state, payout_attempt.unified_code.as_ref(), payout_attempt.unified_message.as_ref(), - &locale, + &state.locale.clone(), ) .await?; let js_data = payouts::PayoutLinkStatusDetails { @@ -322,7 +321,7 @@ pub async fn initiate_payout_link( GenericLinks { allowed_domains, data: GenericLinksData::PayoutLinkStatus(generic_status_data), - locale, + locale: state.locale.clone(), }, ))) } diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index b2d6b2ace45c..3c8432795a37 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -317,7 +317,6 @@ pub async fn payouts_create_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { // Validate create request let (payout_id, payout_method_data, profile_id, customer) = @@ -332,7 +331,7 @@ pub async fn payouts_create_core( &payout_id, &profile_id, payout_method_data.as_ref(), - locale, + &state.locale, customer.as_ref(), ) .await?; @@ -382,7 +381,6 @@ pub async fn payouts_confirm_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -390,7 +388,7 @@ pub async fn payouts_confirm_core( None, &key_store, &payouts::PayoutRequest::PayoutCreateRequest(Box::new(req.to_owned())), - locale, + &state.locale, ) .await?; let payout_attempt = payout_data.payout_attempt.to_owned(); @@ -454,7 +452,6 @@ pub async fn payouts_update_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutCreateRequest, - locale: &str, ) -> RouterResponse { let payout_id = req.payout_id.clone().get_required_value("payout_id")?; let mut payout_data = make_payout_data( @@ -463,7 +460,7 @@ pub async fn payouts_update_core( None, &key_store, &payouts::PayoutRequest::PayoutCreateRequest(Box::new(req.to_owned())), - locale, + &state.locale, ) .await?; @@ -539,7 +536,6 @@ pub async fn payouts_retrieve_core( profile_id: Option, key_store: domain::MerchantKeyStore, req: payouts::PayoutRetrieveRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -547,7 +543,7 @@ pub async fn payouts_retrieve_core( profile_id, &key_store, &payouts::PayoutRequest::PayoutRetrieveRequest(req.to_owned()), - locale, + &state.locale, ) .await?; let payout_attempt = payout_data.payout_attempt.to_owned(); @@ -584,7 +580,6 @@ pub async fn payouts_cancel_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutActionRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -592,7 +587,7 @@ pub async fn payouts_cancel_core( None, &key_store, &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()), - locale, + &state.locale, ) .await?; @@ -678,7 +673,6 @@ pub async fn payouts_fulfill_core( merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: payouts::PayoutActionRequest, - locale: &str, ) -> RouterResponse { let mut payout_data = make_payout_data( &state, @@ -686,7 +680,7 @@ pub async fn payouts_fulfill_core( None, &key_store, &payouts::PayoutRequest::PayoutActionRequest(req.to_owned()), - locale, + &state.locale, ) .await?; @@ -773,7 +767,6 @@ pub async fn payouts_list_core( _profile_id_list: Option>, _key_store: domain::MerchantKeyStore, _constraints: payouts::PayoutListConstraints, - _locale: &str, ) -> RouterResponse { todo!() } @@ -789,7 +782,6 @@ pub async fn payouts_list_core( profile_id_list: Option>, key_store: domain::MerchantKeyStore, constraints: payouts::PayoutListConstraints, - _locale: &str, ) -> RouterResponse { validator::validate_payout_list_request(&constraints)?; let merchant_id = merchant_account.get_id(); @@ -910,7 +902,6 @@ pub async fn payouts_filtered_list_core( profile_id_list: Option>, key_store: domain::MerchantKeyStore, filters: payouts::PayoutListFilterConstraints, - _locale: &str, ) -> RouterResponse { let limit = &filters.limit; validator::validate_payout_list_request_for_joins(*limit)?; @@ -1014,7 +1005,6 @@ pub async fn payouts_list_available_filters_core( merchant_account: domain::MerchantAccount, profile_id_list: Option>, time_range: common_utils::types::TimeRange, - _locale: &str, ) -> RouterResponse { let db = state.store.as_ref(); let payouts = db diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 373a268d6918..e78c9471e61e 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -26,7 +26,7 @@ use crate::{ consts, core::{ errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}, - payments::{self, access_token}, + payments::{self, access_token, helpers}, refunds::transformers::SplitRefundInput, utils as core_utils, }, @@ -116,7 +116,7 @@ pub async fn refund_create_core( req.merchant_connector_details .to_owned() .async_map(|mcd| async { - payments::helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await + helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await }) .await .transpose()?; @@ -237,6 +237,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, }) } errors::ConnectorError::NotSupported { message, connector } => { @@ -249,6 +251,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, }) } _ => None, @@ -284,14 +288,41 @@ pub async fn trigger_refund_to_gateway( }; let refund_update = match router_data_res.response { - Err(err) => storage::RefundUpdate::ErrorUpdate { - refund_status: Some(enums::RefundStatus::Failure), - refund_error_message: err.reason.or(Some(err.message)), - refund_error_code: Some(err.code), - updated_by: storage_scheme.to_string(), - connector_refund_id: None, - connector_refund_data: None, - }, + Err(err) => { + let option_gsm = helpers::get_gsm_record( + state, + Some(err.code.clone()), + Some(err.message.clone()), + connector.connector_name.to_string(), + consts::REFUND_FLOW_STR.to_string(), + ) + .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(), + ) + }; + + storage::RefundUpdate::ErrorUpdate { + refund_status: Some(enums::RefundStatus::Failure), + refund_error_message: err.reason.or(Some(err.message)), + refund_error_code: Some(err.code), + updated_by: storage_scheme.to_string(), + connector_refund_id: None, + connector_refund_data: None, + unified_code: Some(unified_code), + unified_message: Some(unified_message), + } + } Ok(response) => { // match on connector integrity checks match router_data_res.integrity_check.clone() { @@ -319,6 +350,8 @@ pub async fn trigger_refund_to_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, connector_refund_data, + unified_code: None, + unified_message: None, } } Ok(()) => { @@ -461,7 +494,7 @@ pub async fn refund_retrieve_core( .merchant_connector_details .to_owned() .async_map(|mcd| async { - payments::helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await + helpers::insert_merchant_connector_creds_to_config(db, merchant_id, mcd).await }) .await .transpose()?; @@ -479,6 +512,26 @@ pub async fn refund_retrieve_core( }) .transpose()?; + let unified_translated_message = if let (Some(unified_code), Some(unified_message)) = + (refund.unified_code.clone(), refund.unified_message.clone()) + { + helpers::get_unified_translation( + &state, + unified_code, + unified_message.clone(), + state.locale.to_string(), + ) + .await + .or(Some(unified_message)) + } else { + refund.unified_message + }; + + let refund = storage::Refund { + unified_message: unified_translated_message, + ..refund + }; + let response = if should_call_refund(&refund, request.force_sync.unwrap_or(false)) { Box::pin(sync_refund_with_gateway( &state, @@ -617,6 +670,8 @@ pub async fn sync_refund_with_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: None, connector_refund_data: None, + unified_code: None, + unified_message: None, } } Ok(response) => match router_data_res.integrity_check.clone() { @@ -645,6 +700,8 @@ pub async fn sync_refund_with_gateway( updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, connector_refund_data, + unified_code: None, + unified_message: None, } } Ok(()) => { @@ -899,6 +956,25 @@ pub async fn validate_and_create_refund( } } }; + let unified_translated_message = if let (Some(unified_code), Some(unified_message)) = + (refund.unified_code.clone(), refund.unified_message.clone()) + { + helpers::get_unified_translation( + state, + unified_code, + unified_message.clone(), + state.locale.to_string(), + ) + .await + .or(Some(unified_message)) + } else { + refund.unified_message + }; + + let refund = storage::Refund { + unified_message: unified_translated_message, + ..refund + }; Ok(refund.foreign_into()) } @@ -1199,6 +1275,8 @@ impl ForeignFrom for api::RefundResponse { connector: refund.connector, merchant_connector_id: refund.merchant_connector_id, split_refunds: refund.split_refunds, + unified_code: refund.unified_code, + unified_message: refund.unified_message, } } } diff --git a/crates/router/src/db/events.rs b/crates/router/src/db/events.rs index 6bb7de1b7d99..33fcece85a01 100644 --- a/crates/router/src/db/events.rs +++ b/crates/router/src/db/events.rs @@ -734,6 +734,7 @@ mod tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 0abbccd2cb35..fc0fa5aca75f 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1561,6 +1561,7 @@ mod merchant_connector_account_cache_tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -1746,6 +1747,7 @@ mod merchant_connector_account_cache_tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/src/db/merchant_key_store.rs b/crates/router/src/db/merchant_key_store.rs index 9f12ec8e8fd8..aaeba6085a07 100644 --- a/crates/router/src/db/merchant_key_store.rs +++ b/crates/router/src/db/merchant_key_store.rs @@ -350,6 +350,7 @@ mod tests { let state = &Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index 3a5c8b7da1ee..88962f73233a 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -437,6 +437,8 @@ mod storage { organization_id: new.organization_id.clone(), connector_refund_data: new.connector_refund_data.clone(), connector_transaction_data: new.connector_transaction_data.clone(), + unified_code: None, + unified_message: None, }; let field = format!( @@ -932,6 +934,8 @@ impl RefundInterface for MockDb { organization_id: new.organization_id, connector_refund_data: new.connector_refund_data, connector_transaction_data: new.connector_transaction_data, + unified_code: None, + unified_message: None, }; refunds.push(refund.clone()); Ok(refund) diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index f9dbec774528..f8b5ef445678 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -112,6 +112,7 @@ pub struct SessionState { pub opensearch_client: Arc, pub grpc_client: Arc, pub theme_storage_client: Arc, + pub locale: String, } impl scheduler::SchedulerSessionState for SessionState { fn get_db(&self) -> Box { @@ -458,6 +459,7 @@ impl AppState { pub fn get_session_state( self: Arc, tenant: &id_type::TenantId, + locale: Option, err: F, ) -> Result where @@ -484,6 +486,7 @@ impl AppState { opensearch_client: Arc::clone(&self.opensearch_client), grpc_client: Arc::clone(&self.grpc_client), theme_storage_client: self.theme_storage_client.clone(), + locale: locale.unwrap_or(common_utils::consts::DEFAULT_LOCALE.to_string()), }) } } diff --git a/crates/router/src/routes/payment_link.rs b/crates/router/src/routes/payment_link.rs index 71f10fe73e91..361367c7d277 100644 --- a/crates/router/src/routes/payment_link.rs +++ b/crates/router/src/routes/payment_link.rs @@ -65,7 +65,6 @@ pub async fn initiate_payment_link( payment_id, merchant_id: merchant_id.clone(), }; - let headers = req.headers(); Box::pin(api::server_wrap( flow, state, @@ -78,7 +77,6 @@ pub async fn initiate_payment_link( auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), - headers, ) }, &crate::services::authentication::MerchantIdAuth(merchant_id), @@ -183,7 +181,6 @@ pub async fn payment_link_status( payment_id, merchant_id: merchant_id.clone(), }; - let headers = req.headers(); Box::pin(api::server_wrap( flow, state, @@ -196,7 +193,6 @@ pub async fn payment_link_status( auth.key_store, payload.merchant_id.clone(), payload.payment_id.clone(), - headers, ) }, &crate::services::authentication::MerchantIdAuth(merchant_id), diff --git a/crates/router/src/routes/payout_link.rs b/crates/router/src/routes/payout_link.rs index 25528b21ed85..0234b4fca828 100644 --- a/crates/router/src/routes/payout_link.rs +++ b/crates/router/src/routes/payout_link.rs @@ -1,14 +1,12 @@ use actix_web::{web, Responder}; use api_models::payouts::PayoutLinkInitiateRequest; -use common_utils::consts::DEFAULT_LOCALE; use router_env::Flow; use crate::{ core::{api_locking, payout_link::*}, - headers::ACCEPT_LANGUAGE, services::{ api, - authentication::{self as auth, get_header_value_by_key}, + authentication::{self as auth}, }, AppState, }; @@ -25,25 +23,13 @@ pub async fn render_payout_link( payout_id, }; let headers = req.headers(); - let locale = get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers) - .ok() - .flatten() - .map(|val| val.to_string()) - .unwrap_or(DEFAULT_LOCALE.to_string()); Box::pin(api::server_wrap( flow, state, &req, payload.clone(), |state, auth, req, _| { - initiate_payout_link( - state, - auth.merchant_account, - auth.key_store, - req, - headers, - locale.clone(), - ) + initiate_payout_link(state, auth.merchant_account, auth.key_store, req, headers) }, &auth::MerchantIdAuth(merchant_id), api_locking::LockAction::NotApplicable, diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs index 2329a48ef2bc..4044630b4da4 100644 --- a/crates/router/src/routes/payouts.rs +++ b/crates/router/src/routes/payouts.rs @@ -1,31 +1,20 @@ use actix_web::{ body::{BoxBody, MessageBody}, - http::header::HeaderMap, web, HttpRequest, HttpResponse, Responder, }; -use common_utils::consts; use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, payouts::*}, - headers::ACCEPT_LANGUAGE, services::{ api, - authentication::{self as auth, get_header_value_by_key}, + authentication::{self as auth}, authorization::permissions::Permission, }, 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( @@ -34,7 +23,6 @@ pub async fn payouts_create( json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PayoutsCreate; - let locale = get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -42,7 +30,7 @@ pub async fn payouts_create( &req, json_payload.into_inner(), |state, auth: auth::AuthenticationData, req, _| { - payouts_create_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_create_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -65,7 +53,6 @@ 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, @@ -79,7 +66,6 @@ pub async fn payouts_retrieve( auth.profile_id, auth.key_store, req, - &locale, ) }, auth::auth_type( @@ -102,7 +88,6 @@ pub async fn payouts_update( json_payload: web::Json, ) -> 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); @@ -112,7 +97,7 @@ pub async fn payouts_update( &req, payout_update_payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_update_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_update_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -138,7 +123,6 @@ 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, @@ -146,7 +130,7 @@ pub async fn payouts_confirm( &req, payload, |state, auth, req, _| { - payouts_confirm_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_confirm_core(state, auth.merchant_account, auth.key_store, req) }, &*auth_type, api_locking::LockAction::NotApplicable, @@ -165,7 +149,6 @@ 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, @@ -173,7 +156,7 @@ pub async fn payouts_cancel( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_cancel_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_cancel_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -191,7 +174,6 @@ 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, @@ -199,7 +181,7 @@ pub async fn payouts_fulfill( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req, &locale) + payouts_fulfill_core(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -217,7 +199,6 @@ 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, @@ -225,14 +206,7 @@ pub async fn payouts_list( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_list_core( - state, - auth.merchant_account, - None, - auth.key_store, - req, - &locale, - ) + payouts_list_core(state, auth.merchant_account, None, auth.key_store, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -256,7 +230,6 @@ 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, @@ -270,7 +243,6 @@ pub async fn payouts_list_profile( auth.profile_id.map(|profile_id| vec![profile_id]), auth.key_store, req, - &locale, ) }, auth::auth_type( @@ -295,7 +267,6 @@ 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, @@ -303,14 +274,7 @@ pub async fn payouts_list_by_filter( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_filtered_list_core( - state, - auth.merchant_account, - None, - auth.key_store, - req, - &locale, - ) + payouts_filtered_list_core(state, auth.merchant_account, None, auth.key_store, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -334,7 +298,6 @@ 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, @@ -348,7 +311,6 @@ 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( @@ -373,7 +335,6 @@ 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, @@ -381,7 +342,7 @@ pub async fn payouts_list_available_filters_for_merchant( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payouts_list_available_filters_core(state, auth.merchant_account, None, req, &locale) + payouts_list_available_filters_core(state, auth.merchant_account, None, req) }, auth::auth_type( &auth::HeaderAuth(auth::ApiKeyAuth), @@ -405,7 +366,6 @@ 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, @@ -418,7 +378,6 @@ 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/services/api.rs b/crates/router/src/services/api.rs index cac856b2c48b..f90528353cfb 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -79,6 +79,7 @@ use crate::{ generic_link_response::build_generic_link_html, }, types::{self, api, ErrorResponse}, + utils, }; pub type BoxedPaymentConnectorIntegrationInterface = @@ -759,12 +760,14 @@ where )? }; - let mut session_state = Arc::new(app_state.clone()).get_session_state(&tenant_id, || { - errors::ApiErrorResponse::InvalidTenant { - tenant_id: tenant_id.get_string_repr().to_string(), - } - .switch() - })?; + let locale = utils::get_locale_from_header(&incoming_request_header.clone()); + let mut session_state = + Arc::new(app_state.clone()).get_session_state(&tenant_id, Some(locale), || { + errors::ApiErrorResponse::InvalidTenant { + tenant_id: tenant_id.get_string_repr().to_string(), + } + .switch() + })?; session_state.add_request_id(request_id); let mut request_state = session_state.get_req_state(); diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index e4046f95a153..f9ef4880924c 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -55,9 +55,10 @@ use crate::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, payments as payments_core, }, + headers::ACCEPT_LANGUAGE, logger, routes::{metrics, SessionState}, - services, + services::{self, authentication::get_header_value_by_key}, types::{ self, domain, transformers::{ForeignFrom, ForeignInto}, @@ -1324,3 +1325,11 @@ pub async fn trigger_refund_outgoing_webhook( ) -> RouterResult<()> { todo!() } + +pub fn get_locale_from_header(headers: &actix_web::http::header::HeaderMap) -> String { + get_header_value_by_key(ACCEPT_LANGUAGE.into(), headers) + .ok() + .flatten() + .map(|val| val.to_string()) + .unwrap_or(common_utils::consts::DEFAULT_LOCALE.to_string()) +} diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index 55b92b4aace5..a1f85534b6bc 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -20,6 +20,7 @@ async fn invalidate_existing_cache_success() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index e3fd2bc1ee9c..2fbd9a7ed249 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -222,6 +222,7 @@ async fn payments_create_success() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -272,6 +273,7 @@ async fn payments_create_failure() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -338,6 +340,7 @@ async fn refund_for_successful_payments() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -411,6 +414,7 @@ async fn refunds_create_failure() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 361cda63a9f9..1b25e884a3dc 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -604,6 +604,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -647,6 +648,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -691,6 +693,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -734,6 +737,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -828,6 +832,7 @@ pub trait ConnectorActions: Connector { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -868,6 +873,7 @@ async fn call_connector< let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index beaacb79fc01..e1fe40b42065 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -297,6 +297,7 @@ async fn payments_create_core() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -558,6 +559,7 @@ async fn payments_create_core_adyen_no_redirect() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 1d573d007ba6..49d2e12b819f 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -58,6 +58,7 @@ async fn payments_create_core() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -327,6 +328,7 @@ async fn payments_create_core_adyen_no_redirect() { let state = Arc::new(app_state) .get_session_state( &id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/crates/router/tests/services.rs b/crates/router/tests/services.rs index c014370b24f4..36f969dac1cf 100644 --- a/crates/router/tests/services.rs +++ b/crates/router/tests/services.rs @@ -20,6 +20,7 @@ async fn get_redis_conn_failure() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); @@ -51,6 +52,7 @@ async fn get_redis_conn_success() { let state = Arc::new(app_state) .get_session_state( &common_utils::id_type::TenantId::try_from_string("public".to_string()).unwrap(), + None, || {}, ) .unwrap(); diff --git a/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql new file mode 100644 index 000000000000..74679837d38c --- /dev/null +++ b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE refund DROP COLUMN IF EXISTS unified_code; +ALTER TABLE refund DROP COLUMN IF EXISTS unified_message; \ No newline at end of file diff --git a/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql new file mode 100644 index 000000000000..3d350c790f68 --- /dev/null +++ b/migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +ALTER TABLE refund +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