From 77d52955ee336711d7de7577edfbab4520acda0f Mon Sep 17 00:00:00 2001 From: Debarati Date: Fri, 27 Dec 2024 15:16:37 +0530 Subject: [PATCH] add columns unified error code and error message in refund table --- api-reference/openapi_spec.json | 10 ++ crates/api_models/src/refunds.rs | 4 + crates/diesel_models/src/refund.rs | 26 +++++ crates/diesel_models/src/schema.rs | 4 + crates/diesel_models/src/schema_v2.rs | 4 + .../src/connectors/airwallex/transformers.rs | 6 -- crates/router/src/consts.rs | 3 + crates/router/src/core/refunds.rs | 102 ++++++++++++++++-- crates/router/src/db/refund.rs | 4 + crates/router/src/routes/app.rs | 10 ++ crates/router/src/routes/payouts.rs | 38 +++---- crates/router/src/services/api.rs | 3 + crates/router/src/utils.rs | 11 +- .../down.sql | 3 + .../up.sql | 4 + 15 files changed, 190 insertions(+), 42 deletions(-) create mode 100644 migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/down.sql create mode 100644 migrations/2024-12-24-115958_add-unified-code-and-message-in-refunds/up.sql 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..f18ff78717cd 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 = 64] + 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..8d2b4d3ce1f8 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 = 64] + 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/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/refunds.rs b/crates/router/src/core/refunds.rs index 373a268d6918..a3f6b1d22321 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,27 @@ pub async fn refund_retrieve_core( }) .transpose()?; + let locale = state.get_locale(); + 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(), + locale.to_owned(), + ) + .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 +671,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 +701,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 +957,26 @@ pub async fn validate_and_create_refund( } } }; + let locale = state.get_locale(); + 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(), + locale.to_owned(), + ) + .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 +1277,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/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..528a13f08160 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 { @@ -130,6 +131,14 @@ impl SessionState { request_id: self.request_id.map(|req_id| (*req_id).to_string()), } } + + pub fn get_locale(&self) -> &String { + &self.locale + } + + pub fn set_locale(&mut self, locale: String) { + self.locale = locale + } } pub trait SessionStateInfo { @@ -484,6 +493,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: common_utils::consts::DEFAULT_LOCALE.to_string(), }) } } diff --git a/crates/router/src/routes/payouts.rs b/crates/router/src/routes/payouts.rs index 2329a48ef2bc..4d5ba26b842e 100644 --- a/crates/router/src/routes/payouts.rs +++ b/crates/router/src/routes/payouts.rs @@ -1,31 +1,21 @@ 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, + utils, }; -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 +24,7 @@ pub async fn payouts_create( json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PayoutsCreate; - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -65,7 +55,7 @@ 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()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -102,7 +92,7 @@ pub async fn payouts_update( json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PayoutsUpdate; - let locale = get_locale_from_header(req.headers()); + let locale = utils::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); @@ -138,7 +128,7 @@ pub async fn payouts_confirm( Ok(auth) => auth, Err(e) => return api::log_and_return_error_response(e), }; - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -165,7 +155,7 @@ pub async fn payouts_cancel( let flow = Flow::PayoutsCancel; let mut payload = json_payload.into_inner(); payload.payout_id = path.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -191,7 +181,7 @@ pub async fn payouts_fulfill( let flow = Flow::PayoutsFulfill; let mut payload = json_payload.into_inner(); payload.payout_id = path.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -217,7 +207,7 @@ pub async fn payouts_list( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -256,7 +246,7 @@ pub async fn payouts_list_profile( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -295,7 +285,7 @@ pub async fn payouts_list_by_filter( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -334,7 +324,7 @@ pub async fn payouts_list_by_filter_profile( ) -> HttpResponse { let flow = Flow::PayoutsList; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -373,7 +363,7 @@ pub async fn payouts_list_available_filters_for_merchant( ) -> HttpResponse { let flow = Flow::PayoutsFilter; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, @@ -405,7 +395,7 @@ pub async fn payouts_list_available_filters_for_profile( ) -> HttpResponse { let flow = Flow::PayoutsFilter; let payload = json_payload.into_inner(); - let locale = get_locale_from_header(req.headers()); + let locale = utils::get_locale_from_header(req.headers()); Box::pin(api::server_wrap( flow, diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index cac856b2c48b..7af95f539eae 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 = @@ -766,6 +767,8 @@ where .switch() })?; session_state.add_request_id(request_id); + let locale = utils::get_locale_from_header(&incoming_request_header.clone()); + session_state.set_locale(locale); let mut request_state = session_state.get_req_state(); request_state.event_context.record_info(request_id); 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/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