diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 0d750ea7bf53..2f34aa9ec614 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -23213,7 +23213,15 @@ "RelayData": { "oneOf": [ { - "$ref": "#/components/schemas/RelayRefundRequest" + "type": "object", + "required": [ + "refund" + ], + "properties": { + "refund": { + "$ref": "#/components/schemas/RelayRefundRequest" + } + } } ] }, @@ -23269,12 +23277,12 @@ "properties": { "connector_resource_id": { "type": "string", - "description": "The identifier that is associated to a resource at the connector to which the relay request is being made", + "description": "The identifier that is associated to a resource at the connector reference to which the relay request is being made", "example": "7256228702616471803954" }, "connector_id": { "type": "string", - "description": "Identifier of the connector ( merchant connector account ) to which relay request is being made", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", "example": "mca_5apGeP94tMts6rg3U3kR" }, "type": { @@ -23309,11 +23317,10 @@ "status": { "$ref": "#/components/schemas/RelayStatus" }, - "connector_reference_id": { + "connector_resource_id": { "type": "string", - "description": "The reference identifier provided by the connector for the relay request", - "example": "pi_3MKEivSFNglxLpam0ZaL98q9", - "nullable": true + "description": "The identifier that is associated to a resource at the connector reference to which the relay request is being made", + "example": "pi_3MKEivSFNglxLpam0ZaL98q9" }, "error": { "allOf": [ @@ -23323,19 +23330,20 @@ ], "nullable": true }, - "connector_resource_id": { + "connector_reference_id": { "type": "string", "description": "The identifier that is associated to a resource at the connector to which the relay request is being made", - "example": "7256228702616471803954" + "example": "re_3QY4TnEOqOywnAIx1Mm1p7GQ", + "nullable": true }, "connector_id": { "type": "string", - "description": "Identifier of the connector ( merchant connector account ) to which relay request is being made", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", "example": "mca_5apGeP94tMts6rg3U3kR" }, "profile_id": { "type": "string", - "description": "The business profile that is associated with this relay request", + "description": "The business profile that is associated with this relay request.", "example": "pro_abcdefghijklmnopqrstuvwxyz" }, "type": { @@ -23354,8 +23362,9 @@ "RelayStatus": { "type": "string", "enum": [ + "created", + "pending", "success", - "processing", "failure" ] }, diff --git a/crates/api_models/src/relay.rs b/crates/api_models/src/relay.rs index 7e094f283607..e4bc607fc0da 100644 --- a/crates/api_models/src/relay.rs +++ b/crates/api_models/src/relay.rs @@ -1,33 +1,27 @@ -pub use common_utils::types::MinorUnit; +use common_utils::types::MinorUnit; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::enums; +use crate::enums as api_enums; #[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] pub struct RelayRequest { - /// The identifier that is associated to a resource at the connector to which the relay request is being made + /// The identifier that is associated to a resource at the connector reference to which the relay request is being made #[schema(example = "7256228702616471803954")] pub connector_resource_id: String, - /// Identifier of the connector ( merchant connector account ) to which relay request is being made + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment #[schema(example = "mca_5apGeP94tMts6rg3U3kR", value_type = String)] pub connector_id: common_utils::id_type::MerchantConnectorAccountId, /// The type of relay request #[serde(rename = "type")] - pub relay_type: RelayType, + #[schema(value_type = RelayType)] + pub relay_type: api_enums::RelayType, /// The data that is associated with the relay request pub data: Option, } #[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] #[serde(rename_all = "snake_case")] -pub enum RelayType { - /// The relay request is for a refund - Refund, -} - -#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] -#[serde(rename_all = "snake_case", untagged)] pub enum RelayData { /// The data that is associated with a refund relay request Refund(RelayRefundRequest), @@ -40,7 +34,7 @@ pub struct RelayRefundRequest { pub amount: MinorUnit, /// The currency in which the amount is being refunded #[schema(value_type = Currency)] - pub currency: enums::Currency, + pub currency: api_enums::Currency, /// The reason for the refund #[schema(max_length = 255, example = "Customer returned the product")] pub reason: Option, @@ -52,39 +46,30 @@ pub struct RelayResponse { #[schema(example = "relay_mbabizu24mvu3mela5njyhpit4", value_type = String)] pub id: common_utils::id_type::RelayId, /// The status of the relay request - pub status: RelayStatus, - /// The reference identifier provided by the connector for the relay request + #[schema(value_type = RelayStatus)] + pub status: api_enums::RelayStatus, + /// The identifier that is associated to a resource at the connector reference to which the relay request is being made #[schema(example = "pi_3MKEivSFNglxLpam0ZaL98q9")] - pub connector_reference_id: Option, + pub connector_resource_id: String, /// The error details if the relay request failed pub error: Option, /// The identifier that is associated to a resource at the connector to which the relay request is being made - #[schema(example = "7256228702616471803954")] - pub connector_resource_id: String, - /// Identifier of the connector ( merchant connector account ) to which relay request is being made + #[schema(example = "re_3QY4TnEOqOywnAIx1Mm1p7GQ")] + pub connector_reference_id: Option, + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment #[schema(example = "mca_5apGeP94tMts6rg3U3kR", value_type = String)] pub connector_id: common_utils::id_type::MerchantConnectorAccountId, - /// The business profile that is associated with this relay request + /// The business profile that is associated with this relay request. #[schema(example = "pro_abcdefghijklmnopqrstuvwxyz", value_type = String)] pub profile_id: common_utils::id_type::ProfileId, /// The type of relay request #[serde(rename = "type")] - pub relay_type: RelayType, + #[schema(value_type = RelayType)] + pub relay_type: api_enums::RelayType, /// The data that is associated with the relay request pub data: Option, } -#[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] -#[serde(rename_all = "snake_case")] -pub enum RelayStatus { - /// The relay request is successful - Success, - /// The relay request is being processed - Processing, - /// The relay request has failed - Failure, -} - #[derive(Debug, ToSchema, Clone, Deserialize, Serialize)] pub struct RelayError { /// The error code @@ -101,3 +86,7 @@ pub struct RelayRetrieveRequest { /// The unique identifier for the Relay pub id: String, } + +impl common_utils::events::ApiEventMetric for RelayRequest {} + +impl common_utils::events::ApiEventMetric for RelayResponse {} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 5aac1a07457b..193998c3fd1c 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1728,6 +1728,53 @@ pub enum RefundStatus { TransactionFailure, } +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + strum::Display, + strum::EnumString, + strum::EnumIter, + serde::Serialize, + serde::Deserialize, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RelayStatus { + Created, + #[default] + Pending, + Success, + Failure, +} + +#[derive( + Clone, + Copy, + Debug, + Eq, + Hash, + PartialEq, + strum::Display, + strum::EnumString, + strum::EnumIter, + serde::Serialize, + serde::Deserialize, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum RelayType { + Refund, +} + #[derive( Clone, Copy, @@ -3372,6 +3419,26 @@ impl From for TransactionType { } } +impl From for RelayStatus { + fn from(refund_status: RefundStatus) -> Self { + match refund_status { + RefundStatus::Failure | RefundStatus::TransactionFailure => Self::Failure, + RefundStatus::ManualReview | RefundStatus::Pending => Self::Pending, + RefundStatus::Success => Self::Success, + } + } +} + +impl From for RefundStatus { + fn from(relay_status: RelayStatus) -> Self { + match relay_status { + RelayStatus::Failure => Self::Failure, + RelayStatus::Pending | RelayStatus::Created => Self::Pending, + RelayStatus::Success => Self::Success, + } + } +} + #[derive( Clone, Copy, Debug, PartialEq, serde::Serialize, serde::Deserialize, Default, ToSchema, )] diff --git a/crates/common_utils/src/id_type/relay.rs b/crates/common_utils/src/id_type/relay.rs index 5db48c492f9c..3ad64729fb73 100644 --- a/crates/common_utils/src/id_type/relay.rs +++ b/crates/common_utils/src/id_type/relay.rs @@ -4,4 +4,10 @@ crate::id_type!( ); crate::impl_id_type_methods!(RelayId, "relay_id"); +crate::impl_try_from_cow_str_id_type!(RelayId, "relay_id"); +crate::impl_generate_id_id_type!(RelayId, "relay"); +crate::impl_serializable_secret_id_type!(RelayId); +crate::impl_queryable_id_type!(RelayId); +crate::impl_to_sql_from_sql_id_type!(RelayId); + crate::impl_debug_id_type!(RelayId); diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index 19f3bf0a3823..ec6e91a2ecb0 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -17,7 +17,8 @@ pub mod diesel_exports { DbPaymentMethodIssuerCode as PaymentMethodIssuerCode, DbPaymentSource as PaymentSource, DbPaymentType as PaymentType, DbPayoutStatus as PayoutStatus, DbPayoutType as PayoutType, DbProcessTrackerStatus as ProcessTrackerStatus, DbReconStatus as ReconStatus, - DbRefundStatus as RefundStatus, DbRefundType as RefundType, + DbRefundStatus as RefundStatus, DbRefundType as RefundType, DbRelayStatus as RelayStatus, + DbRelayType as RelayType, DbRequestIncrementalAuthorization as RequestIncrementalAuthorization, DbRoleScope as RoleScope, DbRoutingAlgorithmKind as RoutingAlgorithmKind, DbScaExemptionType as ScaExemptionType, diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index cc3dc1361545..1369368a8099 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -39,6 +39,7 @@ pub mod payouts; pub mod process_tracker; pub mod query; pub mod refund; +pub mod relay; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; diff --git a/crates/diesel_models/src/query.rs b/crates/diesel_models/src/query.rs index ab044b5c6e69..8eb0a44f5dd7 100644 --- a/crates/diesel_models/src/query.rs +++ b/crates/diesel_models/src/query.rs @@ -34,6 +34,7 @@ pub mod payout_attempt; pub mod payouts; pub mod process_tracker; pub mod refund; +pub mod relay; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; diff --git a/crates/diesel_models/src/query/relay.rs b/crates/diesel_models/src/query/relay.rs new file mode 100644 index 000000000000..28ede3cd61a8 --- /dev/null +++ b/crates/diesel_models/src/query/relay.rs @@ -0,0 +1,38 @@ +use diesel::{associations::HasTable, ExpressionMethods}; + +use super::generics; +use crate::{ + errors, + relay::{Relay, RelayNew, RelayUpdateInternal}, + schema::relay::dsl, + PgPooledConn, StorageResult, +}; + +impl RelayNew { + pub async fn insert(self, conn: &PgPooledConn) -> StorageResult { + generics::generic_insert(conn, self).await + } +} + +impl Relay { + pub async fn update( + self, + conn: &PgPooledConn, + relay: RelayUpdateInternal, + ) -> StorageResult { + match generics::generic_update_with_unique_predicate_get_result::< + ::Table, + _, + _, + _, + >(conn, dsl::id.eq(self.id.to_owned()), relay) + .await + { + Err(error) => match error.current_context() { + errors::DatabaseError::NoFieldsToUpdate => Ok(self), + _ => Err(error), + }, + result => result, + } + } +} diff --git a/crates/diesel_models/src/relay.rs b/crates/diesel_models/src/relay.rs new file mode 100644 index 000000000000..24a6ab8b034a --- /dev/null +++ b/crates/diesel_models/src/relay.rs @@ -0,0 +1,77 @@ +use common_utils::pii; +use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; +use time::PrimitiveDateTime; + +use crate::{enums as storage_enums, schema::relay}; + +#[derive( + Clone, + Debug, + Eq, + Identifiable, + Queryable, + Selectable, + PartialEq, + serde::Serialize, + serde::Deserialize, +)] +#[diesel(table_name = relay)] +pub struct Relay { + pub id: common_utils::id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub profile_id: common_utils::id_type::ProfileId, + pub merchant_id: common_utils::id_type::MerchantId, + pub relay_type: storage_enums::RelayType, + pub request_data: Option, + pub status: storage_enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +#[derive( + Clone, + Debug, + Eq, + PartialEq, + Insertable, + router_derive::DebugAsDisplay, + serde::Serialize, + serde::Deserialize, + router_derive::Setter, +)] +#[diesel(table_name = relay)] +pub struct RelayNew { + pub id: common_utils::id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, + pub profile_id: common_utils::id_type::ProfileId, + pub merchant_id: common_utils::id_type::MerchantId, + pub relay_type: storage_enums::RelayType, + pub request_data: Option, + pub status: storage_enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +#[derive(Clone, Debug, AsChangeset, router_derive::DebugAsDisplay)] +#[table_name = "relay"] +pub struct RelayUpdateInternal { + pub connector_reference_id: Option, + pub status: Option, + pub error_code: Option, + pub error_message: Option, + pub modified_at: PrimitiveDateTime, +} diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 61a8a7e19b9e..366c917d2d11 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -1235,6 +1235,35 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + relay (id) { + #[max_length = 64] + id -> Varchar, + #[max_length = 128] + connector_resource_id -> Varchar, + #[max_length = 64] + connector_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + relay_type -> RelayType, + request_data -> Nullable, + status -> RelayStatus, + #[max_length = 128] + connector_reference_id -> Nullable, + #[max_length = 64] + error_code -> Nullable, + error_message -> Nullable, + created_at -> Timestamp, + modified_at -> Timestamp, + response_data -> Nullable, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -1479,6 +1508,7 @@ diesel::allow_tables_to_appear_in_same_query!( payouts, process_tracker, refund, + relay, reverse_lookup, roles, routing_algorithm, diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 6569c0353654..fcfe05e5731c 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -1181,6 +1181,35 @@ diesel::table! { } } +diesel::table! { + use diesel::sql_types::*; + use crate::enums::diesel_exports::*; + + relay (id) { + #[max_length = 64] + id -> Varchar, + #[max_length = 128] + connector_resource_id -> Varchar, + #[max_length = 64] + connector_id -> Varchar, + #[max_length = 64] + profile_id -> Varchar, + #[max_length = 64] + merchant_id -> Varchar, + relay_type -> RelayType, + request_data -> Nullable, + status -> RelayStatus, + #[max_length = 128] + connector_reference_id -> Nullable, + #[max_length = 64] + error_code -> Nullable, + error_message -> Nullable, + created_at -> Timestamp, + modified_at -> Timestamp, + response_data -> Nullable, + } +} + diesel::table! { use diesel::sql_types::*; use crate::enums::diesel_exports::*; @@ -1427,6 +1456,7 @@ diesel::allow_tables_to_appear_in_same_query!( payouts, process_tracker, refund, + relay, reverse_lookup, roles, routing_algorithm, diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index fe152e876ccf..7a170f918efc 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -17,6 +17,7 @@ pub mod payments; #[cfg(feature = "payouts")] pub mod payouts; pub mod refunds; +pub mod relay; pub mod router_data; pub mod router_data_v2; pub mod router_flow_types; diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index 51c75113dcda..baec0e59f8a4 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -1,12 +1,11 @@ #[cfg(feature = "v2")] use api_models::admin; -#[cfg(feature = "v2")] -use common_utils::ext_traits::ValueExt; use common_utils::{ crypto::Encryptable, date_time, encryption::Encryption, errors::{CustomResult, ValidationError}, + ext_traits::ValueExt, id_type, pii, type_name, types::keymanager::{Identifier, KeyManagerState, ToEncryptable}, }; @@ -21,9 +20,10 @@ use serde_json::Value; use super::behaviour; #[cfg(feature = "v2")] use crate::errors::api_error_response::ApiErrorResponse; -#[cfg(feature = "v2")] -use crate::router_data; -use crate::type_encryption::{crypto_operation, CryptoOperation}; +use crate::{ + router_data, + type_encryption::{crypto_operation, CryptoOperation}, +}; #[cfg(feature = "v1")] #[derive(Clone, Debug, router_derive::ToEncryption)] @@ -62,6 +62,19 @@ impl MerchantConnectorAccount { pub fn get_id(&self) -> id_type::MerchantConnectorAccountId { self.merchant_connector_id.clone() } + + pub fn get_connector_account_details( + &self, + ) -> error_stack::Result + { + self.connector_account_details + .get_inner() + .clone() + .parse_value("ConnectorAuthType") + } + pub fn get_connector_test_mode(&self) -> Option { + self.test_mode + } } #[cfg(feature = "v2")] @@ -134,6 +147,9 @@ impl MerchantConnectorAccount { .clone() .parse_value("ConnectorAuthType") } + pub fn get_connector_test_mode(&self) -> Option { + todo!() + } } #[cfg(feature = "v1")] diff --git a/crates/hyperswitch_domain_models/src/relay.rs b/crates/hyperswitch_domain_models/src/relay.rs new file mode 100644 index 000000000000..959ac8e7f612 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/relay.rs @@ -0,0 +1,275 @@ +use common_enums::enums; +use common_utils::{ + self, + errors::{CustomResult, ValidationError}, + id_type::{self, GenerateId}, + pii, + types::{keymanager, MinorUnit}, +}; +use diesel_models::relay::RelayUpdateInternal; +use error_stack::ResultExt; +use masking::{ExposeInterface, Secret}; +use serde::{self, Deserialize, Serialize}; +use time::PrimitiveDateTime; + +use crate::{router_data::ErrorResponse, router_response_types}; + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Relay { + pub id: id_type::RelayId, + pub connector_resource_id: String, + pub connector_id: id_type::MerchantConnectorAccountId, + pub profile_id: id_type::ProfileId, + pub merchant_id: id_type::MerchantId, + pub relay_type: enums::RelayType, + pub request_data: Option, + pub status: enums::RelayStatus, + pub connector_reference_id: Option, + pub error_code: Option, + pub error_message: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + pub response_data: Option, +} + +impl Relay { + pub fn new( + relay_request: &api_models::relay::RelayRequest, + merchant_id: &id_type::MerchantId, + profile_id: &id_type::ProfileId, + ) -> Self { + let relay_id = id_type::RelayId::generate(); + Self { + id: relay_id.clone(), + connector_resource_id: relay_request.connector_resource_id.clone(), + connector_id: relay_request.connector_id.clone(), + profile_id: profile_id.clone(), + merchant_id: merchant_id.clone(), + relay_type: common_enums::RelayType::Refund, + request_data: relay_request.data.clone().map(From::from), + status: common_enums::RelayStatus::Created, + connector_reference_id: None, + error_code: None, + error_message: None, + created_at: common_utils::date_time::now(), + modified_at: common_utils::date_time::now(), + response_data: None, + } + } +} + +impl From for RelayData { + fn from(relay: api_models::relay::RelayData) -> Self { + match relay { + api_models::relay::RelayData::Refund(relay_refund_request) => { + Self::Refund(RelayRefundData { + amount: relay_refund_request.amount, + currency: relay_refund_request.currency, + reason: relay_refund_request.reason, + }) + } + } + } +} + +impl RelayUpdate { + pub fn from( + response: Result, + ) -> Self { + match response { + Err(error) => Self::ErrorUpdate { + error_code: error.code, + error_message: error.message, + status: common_enums::RelayStatus::Failure, + }, + Ok(response) => Self::StatusUpdate { + connector_reference_id: Some(response.connector_refund_id), + status: common_enums::RelayStatus::from(response.refund_status), + }, + } + } +} + +impl From for api_models::relay::RelayResponse { + fn from(value: Relay) -> Self { + let error = value + .error_code + .zip(value.error_message) + .map( + |(error_code, error_message)| api_models::relay::RelayError { + code: error_code, + message: error_message, + }, + ); + + let data = value.request_data.map(|relay_data| match relay_data { + RelayData::Refund(relay_refund_request) => { + api_models::relay::RelayData::Refund(api_models::relay::RelayRefundRequest { + amount: relay_refund_request.amount, + currency: relay_refund_request.currency, + reason: relay_refund_request.reason, + }) + } + }); + Self { + id: value.id, + status: value.status, + error, + connector_resource_id: value.connector_resource_id, + connector_id: value.connector_id, + profile_id: value.profile_id, + relay_type: value.relay_type, + data, + connector_reference_id: value.connector_reference_id, + } + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "snake_case", untagged)] +pub enum RelayData { + Refund(RelayRefundData), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct RelayRefundData { + pub amount: MinorUnit, + pub currency: enums::Currency, + pub reason: Option, +} + +#[derive(Debug)] +pub enum RelayUpdate { + ErrorUpdate { + error_code: String, + error_message: String, + status: enums::RelayStatus, + }, + StatusUpdate { + connector_reference_id: Option, + status: common_enums::RelayStatus, + }, +} + +impl From for RelayUpdateInternal { + fn from(value: RelayUpdate) -> Self { + match value { + RelayUpdate::ErrorUpdate { + error_code, + error_message, + status, + } => Self { + error_code: Some(error_code), + error_message: Some(error_message), + connector_reference_id: None, + status: Some(status), + modified_at: common_utils::date_time::now(), + }, + RelayUpdate::StatusUpdate { + connector_reference_id, + status, + } => Self { + connector_reference_id, + status: Some(status), + error_code: None, + error_message: None, + modified_at: common_utils::date_time::now(), + }, + } + } +} + +#[async_trait::async_trait] +impl super::behaviour::Conversion for Relay { + type DstType = diesel_models::relay::Relay; + type NewDstType = diesel_models::relay::RelayNew; + + async fn convert(self) -> CustomResult { + Ok(diesel_models::relay::Relay { + id: self.id, + connector_resource_id: self.connector_resource_id, + connector_id: self.connector_id, + profile_id: self.profile_id, + merchant_id: self.merchant_id, + relay_type: self.relay_type, + request_data: self + .request_data + .map(|data| { + serde_json::to_value(data).change_context(ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }) + }) + .transpose()? + .map(Secret::new), + status: self.status, + connector_reference_id: self.connector_reference_id, + error_code: self.error_code, + error_message: self.error_message, + created_at: self.created_at, + modified_at: self.modified_at, + response_data: self.response_data, + }) + } + + async fn convert_back( + _state: &keymanager::KeyManagerState, + item: Self::DstType, + _key: &Secret>, + _key_manager_identifier: keymanager::Identifier, + ) -> CustomResult { + Ok(Self { + id: item.id, + connector_resource_id: item.connector_resource_id, + connector_id: item.connector_id, + profile_id: item.profile_id, + merchant_id: item.merchant_id, + relay_type: enums::RelayType::Refund, + request_data: item + .request_data + .map(|data| { + serde_json::from_value(data.expose()).change_context( + ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }, + ) + }) + .transpose()?, + status: item.status, + connector_reference_id: item.connector_reference_id, + error_code: item.error_code, + error_message: item.error_message, + created_at: item.created_at, + modified_at: item.modified_at, + response_data: item.response_data, + }) + } + + async fn construct_new(self) -> CustomResult { + Ok(diesel_models::relay::RelayNew { + id: self.id, + connector_resource_id: self.connector_resource_id, + connector_id: self.connector_id, + profile_id: self.profile_id, + merchant_id: self.merchant_id, + relay_type: self.relay_type, + request_data: self + .request_data + .map(|data| { + serde_json::to_value(data).change_context(ValidationError::InvalidValue { + message: "Failed while decrypting business profile data".to_string(), + }) + }) + .transpose()? + .map(Secret::new), + status: self.status, + connector_reference_id: self.connector_reference_id, + error_code: self.error_code, + error_message: self.error_message, + created_at: self.created_at, + modified_at: self.modified_at, + response_data: self.response_data, + }) + } +} diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 7b093ffd9612..daf43fded07e 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -525,11 +525,11 @@ Never share your secret api keys. Keep them guarded and secure. api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, api_models::relay::RelayRequest, - api_models::relay::RelayType, + api_models::relay::RelayResponse, + api_models::enums::RelayType, api_models::relay::RelayData, api_models::relay::RelayRefundRequest, - api_models::relay::RelayResponse, - api_models::relay::RelayStatus, + api_models::enums::RelayStatus, api_models::relay::RelayError, api_models::payments::AmountFilter, api_models::mandates::MandateRevokedResponse, diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index f6fe1d85b5de..c22cecc20f9f 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -54,3 +54,5 @@ pub mod verify_connector; pub mod webhooks; pub mod unified_authentication_service; + +pub mod relay; diff --git a/crates/router/src/core/relay.rs b/crates/router/src/core/relay.rs new file mode 100644 index 000000000000..812709269d83 --- /dev/null +++ b/crates/router/src/core/relay.rs @@ -0,0 +1,177 @@ +use api_models::relay as relay_models; +use common_utils::{self, ext_traits::OptionExt, id_type}; +use error_stack::ResultExt; + +use super::errors::{self, ConnectorErrorExt, RouterResponse, RouterResult, StorageErrorExt}; +use crate::{ + core::payments, + routes::SessionState, + services, + types::{ + api::{self}, + domain, + }, +}; + +pub mod utils; + +pub async fn relay( + state: SessionState, + merchant_account: domain::MerchantAccount, + profile_id_optional: Option, + key_store: domain::MerchantKeyStore, + req: relay_models::RelayRequest, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + let merchant_id = merchant_account.get_id(); + let connector_id = &req.connector_id; + + let profile_id_from_auth_layer = profile_id_optional + .get_required_value("ProfileId") + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "profile id", + })?; + + let profile = db + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + merchant_id, + &profile_id_from_auth_layer, + ) + .await + .change_context(errors::ApiErrorResponse::ProfileNotFound { + id: profile_id_from_auth_layer.get_string_repr().to_owned(), + })?; + + #[cfg(feature = "v1")] + let connector_account = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + connector_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: connector_id.get_string_repr().to_string(), + })?; + + #[cfg(feature = "v2")] + let connector_account = db + .find_merchant_connector_account_by_id(key_manager_state, connector_id, &key_store) + .await + .to_not_found_response(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: connector_id.get_string_repr().to_string(), + })?; + + validate_relay_refund_request(&req).attach_printable("Invalid relay refund request")?; + + let relay_domain = + hyperswitch_domain_models::relay::Relay::new(&req, merchant_id, profile.get_id()); + + let relay_record = db + .insert_relay(key_manager_state, &key_store, relay_domain) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to insert a relay record in db")?; + + let relay_response = match req.relay_type { + common_enums::RelayType::Refund => { + Box::pin(relay_refund( + &state, + merchant_account, + connector_account, + &relay_record, + )) + .await? + } + }; + + let relay_update_record = db + .update_relay(key_manager_state, &key_store, relay_record, relay_response) + .await + .change_context(errors::ApiErrorResponse::InternalServerError)?; + + let response = relay_models::RelayResponse::from(relay_update_record); + + Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( + response, + )) +} + +pub async fn relay_refund( + state: &SessionState, + merchant_account: domain::MerchantAccount, + connector_account: domain::MerchantConnectorAccount, + relay_record: &hyperswitch_domain_models::relay::Relay, +) -> RouterResult { + let connector_id = &relay_record.connector_id; + + let merchant_id = merchant_account.get_id(); + + let connector_data = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_account.connector_name, + api::GetToken::Connector, + Some(connector_id.clone()), + )?; + + let connector_integration: services::BoxedRefundConnectorIntegrationInterface< + api::Execute, + hyperswitch_domain_models::router_request_types::RefundsData, + hyperswitch_domain_models::router_response_types::RefundsResponseData, + > = connector_data.connector.get_connector_integration(); + + let router_data = utils::construct_relay_refund_router_data( + state, + &connector_account.connector_name, + merchant_id, + &connector_account, + relay_record, + ) + .await?; + + let router_data_res = services::execute_connector_processing_step( + state, + connector_integration, + &router_data, + payments::CallConnectorAction::Trigger, + None, + ) + .await + .to_refund_failed_response()?; + + let relay_response = + hyperswitch_domain_models::relay::RelayUpdate::from(router_data_res.response); + + Ok(relay_response) +} + +// validate relay request +pub fn validate_relay_refund_request( + relay_request: &relay_models::RelayRequest, +) -> RouterResult<()> { + match (relay_request.relay_type, &relay_request.data) { + (common_enums::RelayType::Refund, Some(relay_models::RelayData::Refund(ref_data))) => { + validate_relay_refund_data(ref_data) + } + (common_enums::RelayType::Refund, None) => { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Relay data is required for refund relay".to_string(), + })? + } + } +} + +pub fn validate_relay_refund_data( + refund_data: &relay_models::RelayRefundRequest, +) -> RouterResult<()> { + if refund_data.amount.get_amount_as_i64() <= 0 { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Amount should be greater than 0".to_string(), + })? + } + Ok(()) +} diff --git a/crates/router/src/core/relay/utils.rs b/crates/router/src/core/relay/utils.rs new file mode 100644 index 000000000000..0753be1ec435 --- /dev/null +++ b/crates/router/src/core/relay/utils.rs @@ -0,0 +1,139 @@ +use std::str::FromStr; + +use common_utils::{ext_traits::OptionExt, id_type}; +use error_stack::ResultExt; +use hyperswitch_domain_models::{router_data::ErrorResponse, types}; + +use crate::{ + core::payments, + db::{ + domain, + errors::{self, RouterResult}, + }, + routes::SessionState, +}; + +const IRRELEVANT_PAYMENT_INTENT_ID: &str = "irrelevant_payment_intent_id"; + +const IRRELEVANT_PAYMENT_ATTEMPT_ID: &str = "irrelevant_payment_attempt_id"; + +pub async fn construct_relay_refund_router_data<'a, F>( + state: &'a SessionState, + connector_name: &str, + merchant_id: &id_type::MerchantId, + connector_account: &domain::MerchantConnectorAccount, + relay_record: &hyperswitch_domain_models::relay::Relay, +) -> RouterResult> { + let connector_auth_type = connector_account + .get_connector_account_details() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + let webhook_url = Some(payments::helpers::create_webhook_url( + &state.base_url.clone(), + merchant_id, + connector_name, + )); + + let supported_connector = &state + .conf + .multiple_api_version_supported_connectors + .supported_connectors; + + let connector_enum = api_models::enums::Connector::from_str(connector_name) + .change_context(errors::ConnectorError::InvalidConnectorName) + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "connector", + }) + .attach_printable_lazy(|| format!("unable to parse connector name {connector_name:?}"))?; + + let connector_api_version = if supported_connector.contains(&connector_enum) { + state + .store + .find_config_by_key(&format!("connector_api_version_{connector_name}")) + .await + .map(|value| value.config) + .ok() + } else { + None + }; + + let hyperswitch_domain_models::relay::RelayData::Refund(relay_refund_data) = relay_record + .request_data + .clone() + .get_required_value("refund relay data") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to obtain relay data to construct relay refund data")?; + + let relay_id_string = relay_record.id.get_string_repr().to_string(); + + let router_data = hyperswitch_domain_models::router_data::RouterData { + flow: std::marker::PhantomData, + merchant_id: merchant_id.clone(), + customer_id: None, + connector: connector_name.to_string(), + payment_id: IRRELEVANT_PAYMENT_INTENT_ID.to_string(), + attempt_id: IRRELEVANT_PAYMENT_ATTEMPT_ID.to_string(), + status: common_enums::AttemptStatus::Charged, + payment_method: common_enums::PaymentMethod::default(), + connector_auth_type, + description: None, + return_url: None, + address: hyperswitch_domain_models::payment_address::PaymentAddress::default(), + auth_type: common_enums::AuthenticationType::default(), + connector_meta_data: None, + connector_wallets_details: None, + amount_captured: None, + payment_method_status: None, + minor_amount_captured: None, + request: hyperswitch_domain_models::router_request_types::RefundsData { + refund_id: relay_id_string.clone(), + connector_transaction_id: relay_record.connector_resource_id.clone(), + refund_amount: relay_refund_data.amount.get_amount_as_i64(), + minor_refund_amount: relay_refund_data.amount, + currency: relay_refund_data.currency, + payment_amount: relay_refund_data.amount.get_amount_as_i64(), + minor_payment_amount: relay_refund_data.amount, + webhook_url, + connector_metadata: None, + reason: relay_refund_data.reason, + connector_refund_id: relay_record.connector_reference_id.clone(), + browser_info: None, + split_refunds: None, + integrity_object: None, + refund_status: common_enums::RefundStatus::from(relay_record.status), + }, + + response: Err(ErrorResponse::default()), + access_token: None, + session_token: None, + reference_id: None, + payment_method_token: None, + connector_customer: None, + recurring_mandate_payment_data: None, + preprocessing_id: None, + connector_request_reference_id: relay_id_string.clone(), + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + test_mode: connector_account.get_connector_test_mode(), + payment_method_balance: None, + connector_api_version, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: Some(relay_id_string), + dispute_id: None, + connector_response: None, + integrity_check: Ok(()), + additional_merchant_data: None, + header_payload: None, + connector_mandate_request_reference_id: None, + authentication_id: None, + psd2_sca_exemption_type: None, + }; + + Ok(router_data) +} diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index fbb75b6af603..eef84f6ff983 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -30,6 +30,7 @@ pub mod organization; pub mod payment_link; pub mod payment_method; pub mod refund; +pub mod relay; pub mod reverse_lookup; pub mod role; pub mod routing_algorithm; @@ -131,6 +132,7 @@ pub trait StorageInterface: + user_authentication_method::UserAuthenticationMethodInterface + authentication::AuthenticationInterface + generic_link::GenericLinkInterface + + relay::RelayInterface + user::theme::ThemeInterface + 'static { diff --git a/crates/router/src/db/relay.rs b/crates/router/src/db/relay.rs new file mode 100644 index 000000000000..e869165aa339 --- /dev/null +++ b/crates/router/src/db/relay.rs @@ -0,0 +1,135 @@ +use common_utils::types::keymanager::KeyManagerState; +use diesel_models; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::behaviour::{Conversion, ReverseConversion}; +use storage_impl::MockDb; + +use super::domain; +use crate::{ + connection, + core::errors::{self, CustomResult}, + db::kafka_store::KafkaStore, + services::Store, +}; + +#[async_trait::async_trait] +pub trait RelayInterface { + async fn insert_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + new: hyperswitch_domain_models::relay::Relay, + ) -> CustomResult; + + async fn update_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + current_state: hyperswitch_domain_models::relay::Relay, + relay_update: hyperswitch_domain_models::relay::RelayUpdate, + ) -> CustomResult; +} + +#[async_trait::async_trait] +impl RelayInterface for Store { + async fn insert_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + new: hyperswitch_domain_models::relay::Relay, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + new.construct_new() + .await + .change_context(errors::StorageError::EncryptionError)? + .insert(&conn) + .await + .map_err(|error| report!(errors::StorageError::from(error)))? + .convert( + key_manager_state, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError) + } + + async fn update_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + current_state: hyperswitch_domain_models::relay::Relay, + relay_update: hyperswitch_domain_models::relay::RelayUpdate, + ) -> CustomResult { + let conn = connection::pg_connection_write(self).await?; + Conversion::convert(current_state) + .await + .change_context(errors::StorageError::EncryptionError)? + .update( + &conn, + diesel_models::relay::RelayUpdateInternal::from(relay_update), + ) + .await + .map_err(|error| report!(errors::StorageError::from(error)))? + .convert( + key_manager_state, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError) + } +} + +#[async_trait::async_trait] +impl RelayInterface for MockDb { + async fn insert_relay( + &self, + _key_manager_state: &KeyManagerState, + _merchant_key_store: &domain::MerchantKeyStore, + _new: hyperswitch_domain_models::relay::Relay, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } + + async fn update_relay( + &self, + _key_manager_state: &KeyManagerState, + _merchant_key_store: &domain::MerchantKeyStore, + _current_state: hyperswitch_domain_models::relay::Relay, + _relay_update: hyperswitch_domain_models::relay::RelayUpdate, + ) -> CustomResult { + Err(errors::StorageError::MockDbError)? + } +} + +#[async_trait::async_trait] +impl RelayInterface for KafkaStore { + async fn insert_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + new: hyperswitch_domain_models::relay::Relay, + ) -> CustomResult { + self.diesel_store + .insert_relay(key_manager_state, merchant_key_store, new) + .await + } + + async fn update_relay( + &self, + key_manager_state: &KeyManagerState, + merchant_key_store: &domain::MerchantKeyStore, + current_state: hyperswitch_domain_models::relay::Relay, + relay_update: hyperswitch_domain_models::relay::RelayUpdate, + ) -> CustomResult { + self.diesel_store + .update_relay( + key_manager_state, + merchant_key_store, + current_state, + relay_update, + ) + .await + } +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index d43fa0543138..9100ea3ebaa5 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -140,7 +140,8 @@ pub fn mk_app( .service(routes::Customers::server(state.clone())) .service(routes::Configs::server(state.clone())) .service(routes::MerchantConnectorAccount::server(state.clone())) - .service(routes::Webhooks::server(state.clone())); + .service(routes::Webhooks::server(state.clone())) + .service(routes::Relay::server(state.clone())); #[cfg(feature = "oltp")] { diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index aaadcaa4eb51..53e5d5aac71d 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -58,6 +58,8 @@ pub mod verify_connector; pub mod webhook_events; pub mod webhooks; +pub mod relay; + #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; #[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] @@ -66,7 +68,7 @@ pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, Customers, Disputes, EphemeralKey, Files, Forex, Gsm, Health, Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, Profile, ProfileNew, - Refunds, SessionState, User, Webhooks, + Refunds, Relay, SessionState, User, Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 852fd0fbd7eb..a070afe09a48 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -55,7 +55,7 @@ use super::verification::{apple_pay_merchant_registration, retrieve_apple_pay_ve use super::webhooks::*; use super::{ admin, api_keys, cache::*, connector_onboarding, disputes, files, gsm, health::*, profiles, - user, user_role, + relay, user, user_role, }; #[cfg(feature = "v1")] use super::{apple_pay_certificates_migration, blocklist, payment_link, webhook_events}; @@ -585,6 +585,17 @@ impl Payments { } } +pub struct Relay; + +#[cfg(feature = "oltp")] +impl Relay { + pub fn server(state: AppState) -> Scope { + web::scope("/relay") + .app_data(web::Data::new(state)) + .service(web::resource("").route(web::post().to(relay::relay))) + } +} + #[cfg(feature = "v1")] impl Payments { pub fn server(state: AppState) -> Scope { diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index b904cb343a53..4b43c42f3bc1 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -37,6 +37,7 @@ pub enum ApiIdentifier { Recon, Poll, ApplePayCertificatesMigration, + Relay, } impl From for ApiIdentifier { @@ -164,6 +165,7 @@ impl From for ApiIdentifier { | Flow::RefundsFilters | Flow::RefundsAggregate | Flow::RefundsManualUpdate => Self::Refunds, + Flow::Relay => Self::Relay, Flow::FrmFulfillment | Flow::IncomingWebhookReceive diff --git a/crates/router/src/routes/relay.rs b/crates/router/src/routes/relay.rs new file mode 100644 index 000000000000..13c92eb19a6e --- /dev/null +++ b/crates/router/src/routes/relay.rs @@ -0,0 +1,40 @@ +use actix_web::{web, Responder}; +use router_env::{instrument, tracing, Flow}; + +use crate::{ + self as app, + core::{api_locking, relay}, + services::{api, authentication as auth}, +}; + +#[instrument(skip_all, fields(flow = ?Flow::Relay))] +#[cfg(feature = "oltp")] +pub async fn relay( + state: web::Data, + req: actix_web::HttpRequest, + payload: web::Json, +) -> impl Responder { + let flow = Flow::Relay; + let payload = payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, _| { + relay::relay( + state, + auth.merchant_account, + #[cfg(feature = "v1")] + auth.profile_id, + #[cfg(feature = "v2")] + Some(auth.profile.get_id().clone()), + auth.key_store, + req, + ) + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index e8435243ff4d..88608316640a 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -1,3 +1,5 @@ +use std::str::FromStr; + use actix_web::http::header::HeaderMap; #[cfg(all( any(feature = "v2", feature = "v1"), @@ -538,6 +540,15 @@ where .change_context(errors::ApiErrorResponse::Unauthorized) .attach_printable("Failed to fetch merchant key store for the merchant id")?; + let profile_id = + get_header_value_by_key(headers::X_PROFILE_ID.to_string(), request_headers)? + .map(id_type::ProfileId::from_str) + .transpose() + .change_context(errors::ValidationError::IncorrectValueProvided { + field_name: "X-Profile-Id", + }) + .change_context(errors::ApiErrorResponse::Unauthorized)?; + let merchant = state .store() .find_merchant_account_by_merchant_id( @@ -551,7 +562,7 @@ where let auth = AuthenticationData { merchant_account: merchant, key_store, - profile_id: None, + profile_id, }; Ok(( auth.clone(), @@ -3109,7 +3120,7 @@ pub fn get_header_value_by_key(key: String, headers: &HeaderMap) -> RouterResult }) .transpose() } -pub fn get_id_type_by_key_from_headers( +pub fn get_id_type_by_key_from_headers( key: String, headers: &HeaderMap, ) -> RouterResult> { diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 84e322872a9c..de2577e6c34d 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -529,6 +529,8 @@ pub enum Flow { PaymentStartRedirection, /// Volume split on the routing type VolumeSplitOnRoutingType, + /// Relay flow + Relay, } /// Trait for providing generic behaviour to flow metric diff --git a/migrations/2024-12-17-141811_add_relay_table/down.sql b/migrations/2024-12-17-141811_add_relay_table/down.sql new file mode 100644 index 000000000000..47b6682c6791 --- /dev/null +++ b/migrations/2024-12-17-141811_add_relay_table/down.sql @@ -0,0 +1,7 @@ +-- This file should undo anything in `up.sql` +DROP TABLE relay; + +DROP TYPE IF EXISTS "RelayStatus"; + +DROP TYPE IF EXISTS "RelayType"; + diff --git a/migrations/2024-12-17-141811_add_relay_table/up.sql b/migrations/2024-12-17-141811_add_relay_table/up.sql new file mode 100644 index 000000000000..e9f0017bd049 --- /dev/null +++ b/migrations/2024-12-17-141811_add_relay_table/up.sql @@ -0,0 +1,22 @@ +-- Your SQL goes here +CREATE TYPE "RelayStatus" AS ENUM ('created', 'pending', 'failure', 'success'); + +CREATE TYPE "RelayType" AS ENUM ('refund'); + +CREATE TABLE relay ( + id VARCHAR(64) PRIMARY KEY, + connector_resource_id VARCHAR(128) NOT NULL, + connector_id VARCHAR(64) NOT NULL, + profile_id VARCHAR(64) NOT NULL, + merchant_id VARCHAR(64) NOT NULL, + relay_type "RelayType" NOT NULL, + request_data JSONB DEFAULT NULL, + status "RelayStatus" NOT NULL, + connector_reference_id VARCHAR(128), + error_code VARCHAR(64), + error_message TEXT, + created_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + modified_at TIMESTAMP NOT NULL DEFAULT now()::TIMESTAMP, + response_data JSONB DEFAULT NULL +); +