diff --git a/CHANGELOG.md b/CHANGELOG.md index 765cf9e84ef8..8e961ee0b79d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,50 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2024.05.03.1 + +### Bug Fixes + +- **api_request:** Make `payment_method_data` as optional ([#4527](https://github.com/juspay/hyperswitch/pull/4527)) ([`83a1924`](https://github.com/juspay/hyperswitch/commit/83a192466849c5fd201296e7554644a025ced888)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`e3af9d0`](https://github.com/juspay/hyperswitch/commit/e3af9d0cfbf5e73822f3a097d8a36736efae3d3a)) + +**Full Changelog:** [`2024.05.03.0...2024.05.03.1`](https://github.com/juspay/hyperswitch/compare/2024.05.03.0...2024.05.03.1) + +- - - + +## 2024.05.03.0 + +### Features + +- **connector:** + - [Ebanx] Add payout flows ([#4146](https://github.com/juspay/hyperswitch/pull/4146)) ([`4f4cbdf`](https://github.com/juspay/hyperswitch/commit/4f4cbdff21b956b5939cdbe6a4f88f663e6b1281)) + - [Paypal] Add payout flow for wallet(Paypal and Venmo) ([#4406](https://github.com/juspay/hyperswitch/pull/4406)) ([`e4ed1e6`](https://github.com/juspay/hyperswitch/commit/e4ed1e63951873f299f076332671f4a043aa86ab)) +- **core:** Rename crate data_models to hyperswitch_domain_models ([#4504](https://github.com/juspay/hyperswitch/pull/4504)) ([`86e93cd`](https://github.com/juspay/hyperswitch/commit/86e93cd3a0f050c89a82be409b80dc2894143c9e)) +- **opensearch:** Refactoring ([#4244](https://github.com/juspay/hyperswitch/pull/4244)) ([`22cb01a`](https://github.com/juspay/hyperswitch/commit/22cb01ac1ecc90eee464561e4e944aad5cb3ed61)) +- **user:** Add route to get user details ([#4510](https://github.com/juspay/hyperswitch/pull/4510)) ([`be44447`](https://github.com/juspay/hyperswitch/commit/be44447c09ea8814dc8b88aa971e08cc749db5f3)) +- **users:** Create Decision manager for User Flows ([#4518](https://github.com/juspay/hyperswitch/pull/4518)) ([`4b3faf6`](https://github.com/juspay/hyperswitch/commit/4b3faf6781f8ab3198ca86b924f3225256d34b52)) +- Store encrypted extended card info in redis ([#4493](https://github.com/juspay/hyperswitch/pull/4493)) ([`6c59d24`](https://github.com/juspay/hyperswitch/commit/6c59d2434ce5067611d85d37b7ec6f551b7ad81a)) + +### Bug Fixes + +- **users:** Add password validations ([#4489](https://github.com/juspay/hyperswitch/pull/4489)) ([`67794da`](https://github.com/juspay/hyperswitch/commit/67794da36ec25531cbf991034452369b17da8063)) + +### Refactors + +- **Connectors:** [BOA] enhance response objects ([#4508](https://github.com/juspay/hyperswitch/pull/4508)) ([`3ed0e8b`](https://github.com/juspay/hyperswitch/commit/3ed0e8b764d1f1bc7d249122dba39be7dfdcac8b)) +- **user:** Use single purpose token and auth to accept invite ([#4498](https://github.com/juspay/hyperswitch/pull/4498)) ([`4b0cf9c`](https://github.com/juspay/hyperswitch/commit/4b0cf9ce3b793c370e754c159f7f2bf2f8b2a310)) + +### Miscellaneous Tasks + +- **payouts:** Update deployment configs for connector_customer ([#4499](https://github.com/juspay/hyperswitch/pull/4499)) ([`5a447af`](https://github.com/juspay/hyperswitch/commit/5a447afd749c170bfe9f1a106fa28a4819a671dc)) + +**Full Changelog:** [`2024.05.02.0...2024.05.03.0`](https://github.com/juspay/hyperswitch/compare/2024.05.02.0...2024.05.03.0) + +- - - + ## 2024.05.02.0 ### Features diff --git a/Cargo.lock b/Cargo.lock index b4819f9f3791..00c491d7101a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -341,6 +341,7 @@ dependencies = [ "error-stack", "external_services", "futures 0.3.30", + "hyperswitch_domain_models", "hyperswitch_interfaces", "masking", "once_cell", @@ -2401,23 +2402,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7e962a19be5cfc3f3bf6dd8f61eb50107f356ad6270fbb3ed41476571db78be5" -[[package]] -name = "data_models" -version = "0.1.0" -dependencies = [ - "api_models", - "async-trait", - "common_enums", - "common_utils", - "diesel_models", - "error-stack", - "masking", - "serde", - "serde_json", - "thiserror", - "time", -] - [[package]] name = "deadpool" version = "0.10.0" @@ -3618,6 +3602,23 @@ dependencies = [ "tokio 1.37.0", ] +[[package]] +name = "hyperswitch_domain_models" +version = "0.1.0" +dependencies = [ + "api_models", + "async-trait", + "common_enums", + "common_utils", + "diesel_models", + "error-stack", + "masking", + "serde", + "serde_json", + "thiserror", + "time", +] + [[package]] name = "hyperswitch_interfaces" version = "0.1.0" @@ -5592,7 +5593,6 @@ dependencies = [ "config", "cookie 0.18.1", "currency_conversion", - "data_models", "derive_deref", "diesel", "diesel_models", @@ -5608,6 +5608,7 @@ dependencies = [ "hex", "http 0.2.12", "hyper 0.14.28", + "hyperswitch_domain_models", "hyperswitch_interfaces", "image", "infer", @@ -6642,13 +6643,13 @@ dependencies = [ "common_utils", "config", "crc32fast", - "data_models", "diesel", "diesel_models", "dyn-clone", "error-stack", "futures 0.3.30", "http 0.2.12", + "hyperswitch_domain_models", "masking", "mime", "moka", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 1c74e1a1c3a1..8edfef7485e0 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -113,7 +113,7 @@ enabled = true [connector_customer] connector_list = "gocardless,stax,stripe" -payout_connector_list = "wise" +payout_connector_list = "stripe,wise" [delayed_session_response] connectors_with_delayed_session_response = "trustpay,payme" # List of connectors which have delayed session response diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 45a6fec35acd..12724485ea17 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -14,7 +14,7 @@ przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_peka [connector_customer] connector_list = "stax,stripe,gocardless" -payout_connector_list = "wise" +payout_connector_list = "stripe,wise" # Connector configuration, provided attributes will be used to fulfill API requests. # Examples provided here are sandbox/test base urls, can be replaced by live or mock diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 1f404cccb456..6c830c389a64 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -14,7 +14,7 @@ przelewy24.stripe.banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_peka [connector_customer] connector_list = "stax,stripe,gocardless" -payout_connector_list = "wise" +payout_connector_list = "stripe,wise" # Connector configuration, provided attributes will be used to fulfill API requests. # Examples provided here are sandbox/test base urls, can be replaced by live or mock diff --git a/crates/analytics/Cargo.toml b/crates/analytics/Cargo.toml index c3e35519dce0..52680df5c49f 100644 --- a/crates/analytics/Cargo.toml +++ b/crates/analytics/Cargo.toml @@ -11,14 +11,22 @@ license.workspace = true [dependencies] # First party crates -api_models = { version = "0.1.0", path = "../api_models", features = ["errors"] } +api_models = { version = "0.1.0", path = "../api_models", features = [ + "errors", +] } storage_impl = { version = "0.1.0", path = "../storage_impl", default-features = false } common_utils = { version = "0.1.0", path = "../common_utils" } external_services = { version = "0.1.0", path = "../external_services", default-features = false } hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces" } masking = { version = "0.1.0", path = "../masking" } -router_env = { version = "0.1.0", path = "../router_env", features = ["log_extra_implicit_fields", "log_custom_entries_to_extra"] } -diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } +router_env = { version = "0.1.0", path = "../router_env", features = [ + "log_extra_implicit_fields", + "log_custom_entries_to_extra", +] } +diesel_models = { version = "0.1.0", path = "../diesel_models", features = [ + "kv_store", +] } +hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } #Third Party dependencies actix-web = "4.5.1" @@ -34,7 +42,13 @@ once_cell = "1.19.0" reqwest = { version = "0.11.27", features = ["serde_json"] } serde = { version = "1.0.197", features = ["derive", "rc"] } serde_json = "1.0.115" -sqlx = { version = "0.7.3", features = ["postgres", "runtime-tokio", "runtime-tokio-native-tls", "time", "bigdecimal"] } +sqlx = { version = "0.7.3", features = [ + "postgres", + "runtime-tokio", + "runtime-tokio-native-tls", + "time", + "bigdecimal", +] } strum = { version = "0.26.2", features = ["derive"] } thiserror = "1.0.58" time = { version = "0.3.35", features = ["serde", "serde-well-known", "std"] } diff --git a/crates/analytics/src/lib.rs b/crates/analytics/src/lib.rs index eb08d8549d10..2a7075a0f29a 100644 --- a/crates/analytics/src/lib.rs +++ b/crates/analytics/src/lib.rs @@ -10,6 +10,7 @@ pub mod refunds; pub mod api_event; pub mod connector_events; pub mod health_check; +pub mod opensearch; pub mod outgoing_webhook_event; pub mod sdk_events; pub mod search; @@ -668,47 +669,6 @@ pub struct ReportConfig { pub region: String, } -#[derive(Clone, Debug, serde::Deserialize)] -#[serde(tag = "auth")] -#[serde(rename_all = "lowercase")] -pub enum OpensearchAuth { - Basic { username: String, password: String }, - Aws { region: String }, -} - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct OpensearchIndexes { - pub payment_attempts: String, - pub payment_intents: String, - pub refunds: String, - pub disputes: String, -} - -#[derive(Clone, Debug, serde::Deserialize)] -pub struct OpensearchConfig { - host: String, - auth: OpensearchAuth, - indexes: OpensearchIndexes, -} - -impl Default for OpensearchConfig { - fn default() -> Self { - Self { - host: "https://localhost:9200".to_string(), - auth: OpensearchAuth::Basic { - username: "admin".to_string(), - password: "admin".to_string(), - }, - indexes: OpensearchIndexes { - payment_attempts: "hyperswitch-payment-attempt-events".to_string(), - payment_intents: "hyperswitch-payment-intent-events".to_string(), - refunds: "hyperswitch-refund-events".to_string(), - disputes: "hyperswitch-dispute-events".to_string(), - }, - } - } -} - /// Analytics Flow routes Enums /// Info - Dimensions and filters available for the domain /// Filters - Set of values present for the dimension diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs new file mode 100644 index 000000000000..7b19ba0ed06d --- /dev/null +++ b/crates/analytics/src/opensearch.rs @@ -0,0 +1,397 @@ +use api_models::{ + analytics::search::SearchIndex, + errors::types::{ApiError, ApiErrorResponse}, +}; +use aws_config::{self, meta::region::RegionProviderChain, Region}; +use common_utils::errors::{CustomResult, ErrorSwitch}; +use error_stack::ResultExt; +use hyperswitch_domain_models::errors::{StorageError, StorageResult}; +use opensearch::{ + auth::Credentials, + cert::CertificateValidation, + cluster::{Cluster, ClusterHealthParts}, + http::{ + request::JsonBody, + response::Response, + transport::{SingleNodeConnectionPool, Transport, TransportBuilder}, + Url, + }, + MsearchParts, OpenSearch, SearchParts, +}; +use serde_json::{json, Value}; +use storage_impl::errors::ApplicationError; +use strum::IntoEnumIterator; + +use super::{health_check::HealthCheck, query::QueryResult, types::QueryExecutionError}; +use crate::query::QueryBuildingError; + +#[derive(Clone, Debug, serde::Deserialize)] +#[serde(tag = "auth")] +#[serde(rename_all = "lowercase")] +pub enum OpenSearchAuth { + Basic { username: String, password: String }, + Aws { region: String }, +} + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct OpenSearchIndexes { + pub payment_attempts: String, + pub payment_intents: String, + pub refunds: String, + pub disputes: String, +} + +#[derive(Clone, Debug, serde::Deserialize)] +pub struct OpenSearchConfig { + host: String, + auth: OpenSearchAuth, + indexes: OpenSearchIndexes, +} + +impl Default for OpenSearchConfig { + fn default() -> Self { + Self { + host: "https://localhost:9200".to_string(), + auth: OpenSearchAuth::Basic { + username: "admin".to_string(), + password: "admin".to_string(), + }, + indexes: OpenSearchIndexes { + payment_attempts: "hyperswitch-payment-attempt-events".to_string(), + payment_intents: "hyperswitch-payment-intent-events".to_string(), + refunds: "hyperswitch-refund-events".to_string(), + disputes: "hyperswitch-dispute-events".to_string(), + }, + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum OpenSearchError { + #[error("Opensearch connection error")] + ConnectionError, + #[error("Opensearch NON-200 response content: '{0}'")] + ResponseNotOK(String), + #[error("Opensearch response error")] + ResponseError, + #[error("Opensearch query building error")] + QueryBuildingError, +} + +impl ErrorSwitch for QueryBuildingError { + fn switch(&self) -> OpenSearchError { + OpenSearchError::QueryBuildingError + } +} + +impl ErrorSwitch for OpenSearchError { + fn switch(&self) -> ApiErrorResponse { + match self { + Self::ConnectionError => ApiErrorResponse::InternalServerError(ApiError::new( + "IR", + 0, + "Connection error", + None, + )), + Self::ResponseNotOK(response) => ApiErrorResponse::InternalServerError(ApiError::new( + "IR", + 0, + format!("Something went wrong {}", response), + None, + )), + Self::ResponseError => ApiErrorResponse::InternalServerError(ApiError::new( + "IR", + 0, + "Something went wrong", + None, + )), + Self::QueryBuildingError => ApiErrorResponse::InternalServerError(ApiError::new( + "IR", + 0, + "Query building error", + None, + )), + } + } +} + +#[derive(Clone, Debug)] +pub struct OpenSearchClient { + pub client: OpenSearch, + pub transport: Transport, + pub indexes: OpenSearchIndexes, +} + +impl OpenSearchClient { + pub async fn create(conf: &OpenSearchConfig) -> CustomResult { + let url = Url::parse(&conf.host).map_err(|_| OpenSearchError::ConnectionError)?; + let transport = match &conf.auth { + OpenSearchAuth::Basic { username, password } => { + let credentials = Credentials::Basic(username.clone(), password.clone()); + TransportBuilder::new(SingleNodeConnectionPool::new(url)) + .cert_validation(CertificateValidation::None) + .auth(credentials) + .build() + .map_err(|_| OpenSearchError::ConnectionError)? + } + OpenSearchAuth::Aws { region } => { + let region_provider = RegionProviderChain::first_try(Region::new(region.clone())); + let sdk_config = aws_config::from_env().region(region_provider).load().await; + let conn_pool = SingleNodeConnectionPool::new(url); + TransportBuilder::new(conn_pool) + .auth( + sdk_config + .clone() + .try_into() + .map_err(|_| OpenSearchError::ConnectionError)?, + ) + .service_name("es") + .build() + .map_err(|_| OpenSearchError::ConnectionError)? + } + }; + Ok(Self { + transport: transport.clone(), + client: OpenSearch::new(transport), + indexes: conf.indexes.clone(), + }) + } + + pub fn search_index_to_opensearch_index(&self, index: SearchIndex) -> String { + match index { + SearchIndex::PaymentAttempts => self.indexes.payment_attempts.clone(), + SearchIndex::PaymentIntents => self.indexes.payment_intents.clone(), + SearchIndex::Refunds => self.indexes.refunds.clone(), + SearchIndex::Disputes => self.indexes.disputes.clone(), + } + } + + pub async fn execute( + &self, + query_builder: OpenSearchQueryBuilder, + ) -> CustomResult { + match query_builder.query_type { + OpenSearchQuery::Msearch => { + let search_indexes = SearchIndex::iter(); + + let payload = query_builder + .construct_payload(search_indexes.clone().collect()) + .change_context(OpenSearchError::QueryBuildingError)?; + + let payload_with_indexes = payload.into_iter().zip(search_indexes).fold( + Vec::new(), + |mut payload_with_indexes, (index_hit, index)| { + payload_with_indexes.push( + json!({"index": self.search_index_to_opensearch_index(index)}).into(), + ); + payload_with_indexes.push(JsonBody::new(index_hit.clone())); + payload_with_indexes + }, + ); + + self.client + .msearch(MsearchParts::None) + .body(payload_with_indexes) + .send() + .await + .change_context(OpenSearchError::ResponseError) + } + OpenSearchQuery::Search(index) => { + let payload = query_builder + .clone() + .construct_payload(vec![index]) + .change_context(OpenSearchError::QueryBuildingError)?; + + let final_payload = payload.first().unwrap_or(&Value::Null); + + self.client + .search(SearchParts::Index(&[ + &self.search_index_to_opensearch_index(index) + ])) + .from(query_builder.offset.unwrap_or(0)) + .size(query_builder.count.unwrap_or(10)) + .body(final_payload) + .send() + .await + .change_context(OpenSearchError::ResponseError) + } + } + } +} + +#[async_trait::async_trait] +impl HealthCheck for OpenSearchClient { + async fn deep_health_check(&self) -> CustomResult<(), QueryExecutionError> { + let health = Cluster::new(&self.transport) + .health(ClusterHealthParts::None) + .send() + .await + .change_context(QueryExecutionError::DatabaseError)? + .json::() + .await + .change_context(QueryExecutionError::DatabaseError)?; + + if health.status != OpenSearchHealthStatus::Red { + Ok(()) + } else { + Err(QueryExecutionError::DatabaseError.into()) + } + } +} + +impl OpenSearchIndexes { + pub fn validate(&self) -> Result<(), ApplicationError> { + use common_utils::{ext_traits::ConfigExt, fp_utils::when}; + + when(self.payment_attempts.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Payment Attempts index must not be empty".into(), + )) + })?; + + when(self.payment_intents.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Payment Intents index must not be empty".into(), + )) + })?; + + when(self.refunds.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Refunds index must not be empty".into(), + )) + })?; + + when(self.disputes.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Disputes index must not be empty".into(), + )) + })?; + + Ok(()) + } +} + +impl OpenSearchAuth { + pub fn validate(&self) -> Result<(), ApplicationError> { + use common_utils::{ext_traits::ConfigExt, fp_utils::when}; + + match self { + Self::Basic { username, password } => { + when(username.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Basic auth username must not be empty".into(), + )) + })?; + + when(password.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Basic auth password must not be empty".into(), + )) + })?; + } + + Self::Aws { region } => { + when(region.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch Aws auth region must not be empty".into(), + )) + })?; + } + }; + + Ok(()) + } +} + +impl OpenSearchConfig { + pub async fn get_opensearch_client(&self) -> StorageResult { + Ok(OpenSearchClient::create(self) + .await + .map_err(|_| StorageError::InitializationError)?) + } + + pub fn validate(&self) -> Result<(), ApplicationError> { + use common_utils::{ext_traits::ConfigExt, fp_utils::when}; + + when(self.host.is_default_or_empty(), || { + Err(ApplicationError::InvalidConfigurationValueError( + "Opensearch host must not be empty".into(), + )) + })?; + + self.indexes.validate()?; + + self.auth.validate()?; + + Ok(()) + } +} +#[derive(Debug, serde::Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum OpenSearchHealthStatus { + Red, + Green, + Yellow, +} + +#[derive(Debug, serde::Deserialize)] +pub struct OpenSearchHealth { + pub status: OpenSearchHealthStatus, +} + +#[derive(Debug, Clone)] +pub enum OpenSearchQuery { + Msearch, + Search(SearchIndex), +} + +#[derive(Debug, Clone)] +pub struct OpenSearchQueryBuilder { + pub query_type: OpenSearchQuery, + pub query: String, + pub offset: Option, + pub count: Option, + pub filters: Vec<(String, String)>, +} + +impl OpenSearchQueryBuilder { + pub fn new(query_type: OpenSearchQuery, query: String) -> Self { + Self { + query_type, + query, + offset: Default::default(), + count: Default::default(), + filters: Default::default(), + } + } + + pub fn set_offset_n_count(&mut self, offset: i64, count: i64) -> QueryResult<()> { + self.offset = Some(offset); + self.count = Some(count); + Ok(()) + } + + pub fn add_filter_clause(&mut self, lhs: String, rhs: String) -> QueryResult<()> { + self.filters.push((lhs, rhs)); + Ok(()) + } + + pub fn construct_payload(&self, indexes: Vec) -> QueryResult> { + let mut query = + vec![json!({"multi_match": {"type": "phrase", "query": self.query, "lenient": true}})]; + + let mut filters = self + .filters + .iter() + .map(|(k, v)| json!({"match_phrase" : {k : v}})) + .collect::>(); + + query.append(&mut filters); + + // TODO add index specific filters + Ok(indexes + .iter() + .map(|_index| json!({"query": {"bool": {"filter": query}}})) + .collect::>()) + } +} diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs index 4c42e96250b5..dc802ff69486 100644 --- a/crates/analytics/src/search.rs +++ b/crates/analytics/src/search.rs @@ -2,99 +2,33 @@ use api_models::analytics::search::{ GetGlobalSearchRequest, GetSearchRequestWithIndex, GetSearchResponse, OpenMsearchOutput, OpensearchOutput, SearchIndex, }; -use aws_config::{self, meta::region::RegionProviderChain, Region}; -use common_utils::errors::CustomResult; -use opensearch::{ - auth::Credentials, - cert::CertificateValidation, - http::{ - request::JsonBody, - transport::{SingleNodeConnectionPool, TransportBuilder}, - Url, - }, - MsearchParts, OpenSearch, SearchParts, -}; -use serde_json::{json, Value}; +use common_utils::errors::{CustomResult, ReportSwitchExt}; +use error_stack::ResultExt; +use serde_json::Value; use strum::IntoEnumIterator; -use crate::{errors::AnalyticsError, OpensearchAuth, OpensearchConfig, OpensearchIndexes}; - -#[derive(Debug, thiserror::Error)] -pub enum OpensearchError { - #[error("Opensearch connection error")] - ConnectionError, - #[error("Opensearch NON-200 response content: '{0}'")] - ResponseNotOK(String), - #[error("Opensearch response error")] - ResponseError, -} - -pub fn search_index_to_opensearch_index(index: SearchIndex, config: &OpensearchIndexes) -> String { - match index { - SearchIndex::PaymentAttempts => config.payment_attempts.clone(), - SearchIndex::PaymentIntents => config.payment_intents.clone(), - SearchIndex::Refunds => config.refunds.clone(), - SearchIndex::Disputes => config.disputes.clone(), - } -} - -async fn get_opensearch_client(config: OpensearchConfig) -> Result { - let url = Url::parse(&config.host).map_err(|_| OpensearchError::ConnectionError)?; - let transport = match config.auth { - OpensearchAuth::Basic { username, password } => { - let credentials = Credentials::Basic(username, password); - TransportBuilder::new(SingleNodeConnectionPool::new(url)) - .cert_validation(CertificateValidation::None) - .auth(credentials) - .build() - .map_err(|_| OpensearchError::ConnectionError)? - } - OpensearchAuth::Aws { region } => { - let region_provider = RegionProviderChain::first_try(Region::new(region)); - let sdk_config = aws_config::from_env().region(region_provider).load().await; - let conn_pool = SingleNodeConnectionPool::new(url); - TransportBuilder::new(conn_pool) - .auth( - sdk_config - .clone() - .try_into() - .map_err(|_| OpensearchError::ConnectionError)?, - ) - .service_name("es") - .build() - .map_err(|_| OpensearchError::ConnectionError)? - } - }; - Ok(OpenSearch::new(transport)) -} +use crate::opensearch::{ + OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder, +}; pub async fn msearch_results( + client: &OpenSearchClient, req: GetGlobalSearchRequest, merchant_id: &String, - config: OpensearchConfig, -) -> CustomResult, AnalyticsError> { - let client = get_opensearch_client(config.clone()) - .await - .map_err(|_| AnalyticsError::UnknownError)?; +) -> CustomResult, OpenSearchError> { + let mut query_builder = OpenSearchQueryBuilder::new(OpenSearchQuery::Msearch, req.query); - let mut msearch_vector: Vec> = vec![]; - for index in SearchIndex::iter() { - msearch_vector - .push(json!({"index": search_index_to_opensearch_index(index,&config.indexes)}).into()); - msearch_vector.push(json!({"query": {"bool": {"filter": [{"multi_match": {"type": "phrase", "query": req.query, "lenient": true}},{"match_phrase": {"merchant_id": merchant_id}}]}}}).into()); - } + query_builder + .add_filter_clause("merchant_id".to_string(), merchant_id.to_string()) + .switch()?; - let response = client - .msearch(MsearchParts::None) - .body(msearch_vector) - .send() + let response_body = client + .execute(query_builder) .await - .map_err(|_| AnalyticsError::UnknownError)?; - - let response_body = response + .change_context(OpenSearchError::ConnectionError)? .json::>() .await - .map_err(|_| AnalyticsError::UnknownError)?; + .change_context(OpenSearchError::ResponseError)?; Ok(response_body .responses @@ -114,29 +48,30 @@ pub async fn msearch_results( } pub async fn search_results( + client: &OpenSearchClient, req: GetSearchRequestWithIndex, merchant_id: &String, - config: OpensearchConfig, -) -> CustomResult { +) -> CustomResult { let search_req = req.search_req; - let client = get_opensearch_client(config.clone()) - .await - .map_err(|_| AnalyticsError::UnknownError)?; + let mut query_builder = + OpenSearchQueryBuilder::new(OpenSearchQuery::Search(req.index), search_req.query); - let response = client - .search(SearchParts::Index(&[&search_index_to_opensearch_index(req.index.clone(),&config.indexes)])) - .from(search_req.offset) - .size(search_req.count) - .body(json!({"query": {"bool": {"filter": [{"multi_match": {"type": "phrase", "query": search_req.query, "lenient": true}},{"match_phrase": {"merchant_id": merchant_id}}]}}})) - .send() - .await - .map_err(|_| AnalyticsError::UnknownError)?; + query_builder + .add_filter_clause("merchant_id".to_string(), merchant_id.to_string()) + .switch()?; + + query_builder + .set_offset_n_count(search_req.offset, search_req.count) + .switch()?; - let response_body = response + let response_body = client + .execute(query_builder) + .await + .change_context(OpenSearchError::ConnectionError)? .json::>() .await - .map_err(|_| AnalyticsError::UnknownError)?; + .change_context(OpenSearchError::ResponseError)?; Ok(GetSearchResponse { count: response_body.hits.total.value, diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index d4d1b3d80b9e..fde693a37004 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use common_utils::{ + consts, crypto::{Encryptable, OptionalEncryptableName}, pii, }; @@ -1099,8 +1100,47 @@ pub struct ExtendedCardInfoChoice { impl common_utils::events::ApiEventMetric for ExtendedCardInfoChoice {} -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] pub struct ExtendedCardInfoConfig { + /// Merchant public key + #[schema(value_type = String)] pub public_key: Secret, - pub ttl_in_secs: u16, + /// TTL for extended card info + #[schema(default = 900, maximum = 3600, value_type = u16)] + #[serde(default)] + pub ttl_in_secs: TtlForExtendedCardInfo, +} + +#[derive(Debug, serde::Serialize, Clone)] +pub struct TtlForExtendedCardInfo(u16); + +impl Default for TtlForExtendedCardInfo { + fn default() -> Self { + Self(consts::DEFAULT_TTL_FOR_EXTENDED_CARD_INFO) + } +} + +impl<'de> Deserialize<'de> for TtlForExtendedCardInfo { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + let value = u16::deserialize(deserializer)?; + + // Check if value exceeds the maximum allowed + if value > consts::MAX_TTL_FOR_EXTENDED_CARD_INFO { + Err(serde::de::Error::custom( + "ttl_in_secs must be less than or equal to 3600 (1hr)", + )) + } else { + Ok(Self(value)) + } + } +} + +impl std::ops::Deref for TtlForExtendedCardInfo { + type Target = u16; + fn deref(&self) -> &Self::Target { + &self.0 + } } diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs index 3a5b3c307e6c..6f6a3f228128 100644 --- a/crates/api_models/src/analytics/search.rs +++ b/crates/api_models/src/analytics/search.rs @@ -30,7 +30,7 @@ pub struct GetSearchRequestWithIndex { pub search_req: GetSearchRequest, } -#[derive(Debug, strum::EnumIter, Clone, serde::Deserialize, serde::Serialize)] +#[derive(Debug, strum::EnumIter, Clone, serde::Deserialize, serde::Serialize, Copy)] #[serde(rename_all = "snake_case")] pub enum SearchIndex { PaymentAttempts, diff --git a/crates/api_models/src/events/user.rs b/crates/api_models/src/events/user.rs index 9884dbc4f416..594b60b58162 100644 --- a/crates/api_models/src/events/user.rs +++ b/crates/api_models/src/events/user.rs @@ -12,10 +12,11 @@ use crate::user::{ }, AcceptInviteFromEmailRequest, AuthorizeResponse, ChangePasswordRequest, ConnectAccountRequest, CreateInternalUserRequest, DashboardEntryResponse, ForgotPasswordRequest, - GetUserDetailsRequest, GetUserDetailsResponse, InviteUserRequest, ListUsersResponse, - ReInviteUserRequest, ResetPasswordRequest, SendVerifyEmailRequest, SignInResponse, - SignUpRequest, SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, - UpdateUserAccountDetailsRequest, UserMerchantCreate, VerifyEmailRequest, + GetUserDetailsResponse, GetUserRoleDetailsRequest, GetUserRoleDetailsResponse, + InviteUserRequest, ListUsersResponse, ReInviteUserRequest, ResetPasswordRequest, + SendVerifyEmailRequest, SignInResponse, SignInWithTokenResponse, SignUpRequest, + SignUpWithMerchantIdRequest, SwitchMerchantIdRequest, UpdateUserAccountDetailsRequest, + UserMerchantCreate, VerifyEmailRequest, }; impl ApiEventMetric for DashboardEntryResponse { @@ -60,8 +61,10 @@ common_utils::impl_misc_api_event_type!( AcceptInviteFromEmailRequest, SignInResponse, UpdateUserAccountDetailsRequest, - GetUserDetailsRequest, - GetUserDetailsResponse + GetUserDetailsResponse, + SignInWithTokenResponse, + GetUserRoleDetailsRequest, + GetUserRoleDetailsResponse ); #[cfg(feature = "dummy_connector")] diff --git a/crates/api_models/src/health_check.rs b/crates/api_models/src/health_check.rs index 29a59df397e9..1e86e2964c78 100644 --- a/crates/api_models/src/health_check.rs +++ b/crates/api_models/src/health_check.rs @@ -6,6 +6,8 @@ pub struct RouterHealthCheckResponse { pub vault: Option, #[cfg(feature = "olap")] pub analytics: bool, + #[cfg(feature = "olap")] + pub opensearch: bool, pub outgoing_request: bool, } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index d730fa8c859d..b39355d2c991 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -916,6 +916,58 @@ pub struct Card { pub nick_name: Option>, } +#[derive(Default, Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct ExtendedCardInfo { + /// The card number + #[schema(value_type = String, example = "4242424242424242")] + pub card_number: CardNumber, + + /// The card's expiry month + #[schema(value_type = String, example = "24")] + pub card_exp_month: Secret, + + /// The card's expiry year + #[schema(value_type = String, example = "24")] + pub card_exp_year: Secret, + + /// The card holder's name + #[schema(value_type = String, example = "John Test")] + pub card_holder_name: Option>, + + /// The name of the issuer of card + #[schema(example = "chase")] + pub card_issuer: Option, + + /// The card network for the card + #[schema(value_type = Option, example = "Visa")] + pub card_network: Option, + + #[schema(example = "CREDIT")] + pub card_type: Option, + + #[schema(example = "INDIA")] + pub card_issuing_country: Option, + + #[schema(example = "JP_AMEX")] + pub bank_code: Option, +} + +impl From for ExtendedCardInfo { + fn from(value: Card) -> Self { + Self { + card_number: value.card_number, + card_exp_month: value.card_exp_month, + card_exp_year: value.card_exp_year, + card_holder_name: value.card_holder_name, + card_issuer: value.card_issuer, + card_network: value.card_network, + card_type: value.card_type, + card_issuing_country: value.card_issuing_country, + bank_code: value.bank_code, + } + } +} + impl GetAddressFromPaymentMethodData for Card { fn get_billing_address(&self) -> Option
{ // Create billing address if first_name is some or if it is not "" @@ -1236,15 +1288,56 @@ mod payment_method_data_serde { OptionalPaymentMethod(serde_json::Value), } + // This struct is an intermediate representation + // This is required in order to catch deserialization errors when deserializing `payment_method_data` + // The #[serde(flatten)] attribute applied on `payment_method_data` discards + // any of the error when deserializing and deserializes to an option instead + #[derive(serde::Deserialize, Debug)] + struct __InnerPaymentMethodData { + billing: Option
, + #[serde(flatten)] + payment_method_data: Option, + } + let deserialize_to_inner = __Inner::deserialize(deserializer)?; + match deserialize_to_inner { __Inner::OptionalPaymentMethod(value) => { - let parsed_value = serde_json::from_value::(value) + let parsed_value = serde_json::from_value::<__InnerPaymentMethodData>(value) .map_err(|serde_json_error| { serde::de::Error::custom(serde_json_error.to_string()) })?; - Ok(Some(parsed_value)) + let payment_method_data = if let Some(payment_method_data_value) = + parsed_value.payment_method_data + { + // Even though no data is passed, the flatten serde_json::Value is deserialized as Some(Object {}) + if let serde_json::Value::Object(ref inner_map) = payment_method_data_value { + if inner_map.is_empty() { + None + } else { + Some( + serde_json::from_value::( + payment_method_data_value, + ) + .map_err(|serde_json_error| { + serde::de::Error::custom(serde_json_error.to_string()) + })?, + ) + } + } else { + Err(serde::de::Error::custom( + "Expected a map for payment_method_data", + ))? + } + } else { + None + }; + + Ok(Some(PaymentMethodDataRequest { + payment_method_data, + billing: parsed_value.billing, + })) } __Inner::RewardString(inner_string) => { let payment_method_data = match inner_string.as_str() { @@ -1253,7 +1346,7 @@ mod payment_method_data_serde { }; Ok(Some(PaymentMethodDataRequest { - payment_method_data, + payment_method_data: Some(payment_method_data), billing: None, })) } @@ -1268,21 +1361,29 @@ mod payment_method_data_serde { S: Serializer, { if let Some(payment_method_data_request) = payment_method_data_request { - match payment_method_data_request.payment_method_data { - PaymentMethodData::Reward => serializer.serialize_str("reward"), - PaymentMethodData::CardRedirect(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankRedirect(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::Upi(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::Card(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::Wallet(_) => payment_method_data_request.serialize(serializer), + if let Some(payment_method_data) = + payment_method_data_request.payment_method_data.as_ref() + { + match payment_method_data { + PaymentMethodData::Reward => serializer.serialize_str("reward"), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::Card(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Wallet(_) => { + payment_method_data_request.serialize(serializer) + } + } + } else { + payment_method_data_request.serialize(serializer) } } else { serializer.serialize_none() @@ -1293,7 +1394,7 @@ mod payment_method_data_serde { #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema, Eq, PartialEq)] pub struct PaymentMethodDataRequest { #[serde(flatten)] - pub payment_method_data: PaymentMethodData, + pub payment_method_data: Option, /// billing details for the payment method. /// This billing details will be passed to the processor as billing address. /// If not passed, then payment.billing will be considered @@ -4663,7 +4764,7 @@ mod payments_request_api_contract { let payments_request = serde_json::from_str::(payments_request); assert!(payments_request.is_ok()); - if let PaymentMethodData::Card(card_data) = payments_request + if let Some(PaymentMethodData::Card(card_data)) = payments_request .unwrap() .payment_method_data .unwrap() @@ -4695,7 +4796,7 @@ mod payments_request_api_contract { .payment_method_data .unwrap() .payment_method_data, - PaymentMethodData::Reward + Some(PaymentMethodData::Reward) ); } } diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index 891033c1aea2..815234d57ed0 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -289,6 +289,7 @@ pub struct PixBankTransfer { #[serde(rename_all = "snake_case")] pub enum Wallet { Paypal(Paypal), + Venmo(Venmo), } #[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] @@ -296,6 +297,21 @@ pub struct Paypal { /// Email linked with paypal account #[schema(value_type = String, example = "john.doe@example.com")] pub email: Option, + + /// mobile number linked to paypal account + #[schema(value_type = String, example = "16608213349")] + pub telephone_number: Option>, + + /// id of the paypal account + #[schema(value_type = String, example = "G83KXTJ5EHCQ2")] + pub paypal_id: Option>, +} + +#[derive(Default, Eq, PartialEq, Clone, Debug, Deserialize, Serialize, ToSchema)] +pub struct Venmo { + /// mobile number linked to venmo account + #[schema(value_type = String, example = "16608213349")] + pub telephone_number: Option>, } #[derive(Debug, Default, ToSchema, Clone, Serialize)] diff --git a/crates/api_models/src/user.rs b/crates/api_models/src/user.rs index 9b3d7a2e654a..b4d53a92c1a6 100644 --- a/crates/api_models/src/user.rs +++ b/crates/api_models/src/user.rs @@ -1,4 +1,4 @@ -use common_enums::{PermissionGroup, RoleScope}; +use common_enums::{PermissionGroup, RoleScope, TokenPurpose}; use common_utils::{crypto::OptionalEncryptableName, pii}; use masking::Secret; @@ -149,13 +149,25 @@ pub struct UserDetails { pub last_modified_at: time::PrimitiveDateTime, } +#[derive(serde::Serialize, Debug, Clone)] +pub struct GetUserDetailsResponse { + pub merchant_id: String, + pub name: Secret, + pub email: pii::Email, + pub verification_days_left: Option, + pub role_id: String, + // This field is added for audit/debug reasons + #[serde(skip_serializing)] + pub user_id: String, + pub org_id: String, +} #[derive(Debug, serde::Deserialize, serde::Serialize)] -pub struct GetUserDetailsRequest { +pub struct GetUserRoleDetailsRequest { pub email: pii::Email, } #[derive(Debug, serde::Serialize)] -pub struct GetUserDetailsResponse { +pub struct GetUserRoleDetailsResponse { pub email: pii::Email, pub name: Secret, pub role_id: String, @@ -201,3 +213,21 @@ pub struct UpdateUserAccountDetailsRequest { pub name: Option>, pub preferred_merchant_id: Option, } + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TokenOnlyQueryParam { + pub token_only: Option, +} + +#[derive(Debug, serde::Deserialize, serde::Serialize)] +pub struct TokenResponse { + pub token: Secret, + pub token_type: TokenPurpose, +} + +#[derive(Debug, serde::Serialize)] +#[serde(untagged)] +pub enum SignInWithTokenResponse { + Token(TokenResponse), + SignInResponse(SignInResponse), +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 5323a2dcf5a5..3c86064731b5 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1429,6 +1429,7 @@ pub enum PaymentMethodType { Twint, UpiCollect, Vipps, + Venmo, Walley, WeChatPay, SevenEleven, @@ -2705,3 +2706,17 @@ pub enum BankHolderType { Personal, Business, } + +#[derive(Debug, Clone, PartialEq, Eq, strum::Display, serde::Deserialize, serde::Serialize)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum TokenPurpose { + #[serde(rename = "totp")] + #[strum(serialize = "totp")] + TOTP, + VerifyEmail, + AcceptInvitationFromEmail, + ResetPassword, + AcceptInvite, + UserInfo, +} diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index 922d2a71c1bb..faca2579c0a4 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -1855,6 +1855,7 @@ impl From for PaymentMethod { PaymentMethodType::Twint => Self::Wallet, PaymentMethodType::UpiCollect => Self::Upi, PaymentMethodType::Vipps => Self::Wallet, + PaymentMethodType::Venmo => Self::Wallet, PaymentMethodType::Walley => Self::PayLater, PaymentMethodType::WeChatPay => Self::Wallet, PaymentMethodType::TouchNGo => Self::Wallet, diff --git a/crates/common_utils/src/consts.rs b/crates/common_utils/src/consts.rs index d9d1f18c347c..509056152ebc 100644 --- a/crates/common_utils/src/consts.rs +++ b/crates/common_utils/src/consts.rs @@ -77,3 +77,9 @@ pub const DEFAULT_DISPLAY_SDK_ONLY: bool = false; /// Default bool to enable saved payment method pub const DEFAULT_ENABLE_SAVED_PAYMENT_METHOD: bool = false; + +/// Default ttl for Extended card info in redis (in seconds) +pub const DEFAULT_TTL_FOR_EXTENDED_CARD_INFO: u16 = 15 * 60; + +/// Max ttl for Extended card info in redis (in seconds) +pub const MAX_TTL_FOR_EXTENDED_CARD_INFO: u16 = 60 * 60; diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 22ab02358662..941fc9d74656 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -89,6 +89,7 @@ pub enum WalletType { TouchNGo, Swish, Cashapp, + Venmo, } #[derive( diff --git a/crates/euclid/src/frontend/dir/lowering.rs b/crates/euclid/src/frontend/dir/lowering.rs index f89877ca21a7..f6b156bf9093 100644 --- a/crates/euclid/src/frontend/dir/lowering.rs +++ b/crates/euclid/src/frontend/dir/lowering.rs @@ -56,6 +56,7 @@ impl From for global_enums::PaymentMethodType { enums::WalletType::TouchNGo => Self::TouchNGo, enums::WalletType::Swish => Self::Swish, enums::WalletType::Cashapp => Self::Cashapp, + enums::WalletType::Venmo => Self::Venmo, } } } diff --git a/crates/euclid/src/frontend/dir/transformers.rs b/crates/euclid/src/frontend/dir/transformers.rs index 929b878f2c2f..bcc951b00574 100644 --- a/crates/euclid/src/frontend/dir/transformers.rs +++ b/crates/euclid/src/frontend/dir/transformers.rs @@ -167,6 +167,7 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet global_enums::PaymentMethodType::CardRedirect => { Ok(dirval!(CardRedirectType = CardRedirect)) } + global_enums::PaymentMethodType::Venmo => Ok(dirval!(WalletType = Venmo)), } } } diff --git a/crates/data_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml similarity index 96% rename from crates/data_models/Cargo.toml rename to crates/hyperswitch_domain_models/Cargo.toml index 54130ca002a5..58eff10a3635 100644 --- a/crates/data_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "data_models" +name = "hyperswitch_domain_models" description = "Represents the data/domain models used by the business layer" version = "0.1.0" edition.workspace = true diff --git a/crates/data_models/README.md b/crates/hyperswitch_domain_models/README.md similarity index 67% rename from crates/data_models/README.md rename to crates/hyperswitch_domain_models/README.md index 0c1c51705588..d0b974d13c64 100644 --- a/crates/data_models/README.md +++ b/crates/hyperswitch_domain_models/README.md @@ -1,3 +1,3 @@ -# Data models +# Hyperswitch domain models Represents the data/domain models used by the business/domain layer \ No newline at end of file diff --git a/crates/data_models/src/errors.rs b/crates/hyperswitch_domain_models/src/errors.rs similarity index 100% rename from crates/data_models/src/errors.rs rename to crates/hyperswitch_domain_models/src/errors.rs diff --git a/crates/data_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs similarity index 100% rename from crates/data_models/src/lib.rs rename to crates/hyperswitch_domain_models/src/lib.rs diff --git a/crates/data_models/src/mandates.rs b/crates/hyperswitch_domain_models/src/mandates.rs similarity index 100% rename from crates/data_models/src/mandates.rs rename to crates/hyperswitch_domain_models/src/mandates.rs diff --git a/crates/data_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs similarity index 100% rename from crates/data_models/src/payments.rs rename to crates/hyperswitch_domain_models/src/payments.rs diff --git a/crates/data_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs similarity index 100% rename from crates/data_models/src/payments/payment_attempt.rs rename to crates/hyperswitch_domain_models/src/payments/payment_attempt.rs diff --git a/crates/data_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs similarity index 100% rename from crates/data_models/src/payments/payment_intent.rs rename to crates/hyperswitch_domain_models/src/payments/payment_intent.rs diff --git a/crates/data_models/src/payouts.rs b/crates/hyperswitch_domain_models/src/payouts.rs similarity index 100% rename from crates/data_models/src/payouts.rs rename to crates/hyperswitch_domain_models/src/payouts.rs diff --git a/crates/data_models/src/payouts/payout_attempt.rs b/crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs similarity index 100% rename from crates/data_models/src/payouts/payout_attempt.rs rename to crates/hyperswitch_domain_models/src/payouts/payout_attempt.rs diff --git a/crates/data_models/src/payouts/payouts.rs b/crates/hyperswitch_domain_models/src/payouts/payouts.rs similarity index 100% rename from crates/data_models/src/payouts/payouts.rs rename to crates/hyperswitch_domain_models/src/payouts/payouts.rs diff --git a/crates/kgraph_utils/src/transformers.rs b/crates/kgraph_utils/src/transformers.rs index 000a16a26348..3e43a4324f94 100644 --- a/crates/kgraph_utils/src/transformers.rs +++ b/crates/kgraph_utils/src/transformers.rs @@ -286,6 +286,7 @@ impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { api_enums::PaymentMethodType::CardRedirect => { Ok(dirval!(CardRedirectType = CardRedirect)) } + api_enums::PaymentMethodType::Venmo => Ok(dirval!(WalletType = Venmo)), } } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index aa30a480406d..1e3844083972 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -194,6 +194,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::MerchantConnectorDeleteResponse, api_models::admin::MerchantConnectorResponse, api_models::admin::AuthenticationConnectorDetails, + api_models::admin::ExtendedCardInfoConfig, api_models::customers::CustomerRequest, api_models::customers::CustomerDeleteResponse, api_models::payment_methods::PaymentMethodCreate, @@ -407,6 +408,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ThreeDsMethodData, api_models::payments::PollConfigResponse, api_models::payments::ExternalAuthenticationDetailsResponse, + api_models::payments::ExtendedCardInfo, api_models::payment_methods::RequiredFieldInfo, api_models::payment_methods::DefaultPaymentMethod, api_models::payment_methods::MaskedBankDetails, @@ -428,6 +430,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payouts::Card, api_models::payouts::Wallet, api_models::payouts::Paypal, + api_models::payouts::Venmo, api_models::payouts::AchBankTransfer, api_models::payouts::BacsBankTransfer, api_models::payouts::SepaBankTransfer, diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 8915ab573e0b..f8e2cfad1268 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -14,7 +14,7 @@ email = ["external_services/email", "scheduler/email", "olap"] frm = ["api_models/frm"] stripe = ["dep:serde_qs"] release = ["stripe", "email", "backwards_compatibility", "business_profile_routing", "accounts_cache", "kv_store", "connector_choice_mca_id", "profile_specific_fallback_routing", "vergen", "recon", "external_services/aws_kms", "external_services/aws_s3"] -olap = ["data_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] +olap = ["hyperswitch_domain_models/olap", "storage_impl/olap", "scheduler/olap", "api_models/olap", "dep:analytics"] oltp = ["storage_impl/oltp"] kv_store = ["scheduler/kv_store"] accounts_cache = [] @@ -26,7 +26,7 @@ dummy_connector = ["api_models/dummy_connector", "euclid/dummy_connector", "kgra connector_choice_mca_id = ["api_models/connector_choice_mca_id", "euclid/connector_choice_mca_id", "kgraph_utils/connector_choice_mca_id"] external_access_dc = ["dummy_connector"] detailed_errors = ["api_models/detailed_errors", "error-stack/serde"] -payouts = ["api_models/payouts", "common_enums/payouts", "data_models/payouts", "storage_impl/payouts"] +payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_domain_models/payouts", "storage_impl/payouts"] payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] @@ -104,7 +104,7 @@ cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs"] } currency_conversion = { version = "0.1.0", path = "../currency_conversion" } -data_models = { version = "0.1.0", path = "../data_models", default-features = false } +hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } diesel_models = { version = "0.1.0", path = "../diesel_models", features = ["kv_store"] } euclid = { version = "0.1.0", path = "../euclid", features = ["valued_jit"] } pm_auth = { version = "0.1.0", path = "../pm_auth", package = "pm_auth" } diff --git a/crates/router/src/analytics.rs b/crates/router/src/analytics.rs index 0aec94c205c5..d509cf03d3c1 100644 --- a/crates/router/src/analytics.rs +++ b/crates/router/src/analytics.rs @@ -614,9 +614,9 @@ pub mod routes { json_payload.into_inner(), |state, auth: AuthenticationData, req, _| async move { analytics::search::msearch_results( + &state.opensearch_client, req, &auth.merchant_account.merchant_id, - state.conf.opensearch.clone(), ) .await .map(ApplicationResponse::Json) @@ -645,9 +645,9 @@ pub mod routes { indexed_req, |state, auth: AuthenticationData, req, _| async move { analytics::search::search_results( + &state.opensearch_client, req, &auth.merchant_account.merchant_id, - state.conf.opensearch.clone(), ) .await .map(ApplicationResponse::Json) diff --git a/crates/router/src/compatibility/stripe/payment_intents/types.rs b/crates/router/src/compatibility/stripe/payment_intents/types.rs index 404d0e93db71..eed80a1128e9 100644 --- a/crates/router/src/compatibility/stripe/payment_intents/types.rs +++ b/crates/router/src/compatibility/stripe/payment_intents/types.rs @@ -335,7 +335,9 @@ impl TryFrom for payments::PaymentsRequest { pmd.payment_method_details .as_ref() .map(|spmd| payments::PaymentMethodDataRequest { - payment_method_data: payments::PaymentMethodData::from(spmd.to_owned()), + payment_method_data: Some(payments::PaymentMethodData::from( + spmd.to_owned(), + )), billing: pmd.billing_details.clone().map(payments::Address::from), }) }), diff --git a/crates/router/src/compatibility/stripe/setup_intents/types.rs b/crates/router/src/compatibility/stripe/setup_intents/types.rs index d29bf235b576..03335d427228 100644 --- a/crates/router/src/compatibility/stripe/setup_intents/types.rs +++ b/crates/router/src/compatibility/stripe/setup_intents/types.rs @@ -246,7 +246,9 @@ impl TryFrom for payments::PaymentsRequest { pmd.payment_method_details .as_ref() .map(|spmd| payments::PaymentMethodDataRequest { - payment_method_data: payments::PaymentMethodData::from(spmd.to_owned()), + payment_method_data: Some(payments::PaymentMethodData::from( + spmd.to_owned(), + )), billing: pmd.billing_details.clone().map(payments::Address::from), }) }), diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 810ab5b98ed2..01b05c60bd95 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -4,7 +4,7 @@ use std::{ }; #[cfg(feature = "olap")] -use analytics::{OpensearchConfig, ReportConfig}; +use analytics::{opensearch::OpenSearchConfig, ReportConfig}; use api_models::{enums, payment_methods::RequiredFieldInfo}; use common_utils::ext_traits::ConfigExt; use config::{Environment, File}; @@ -114,7 +114,7 @@ pub struct Settings { #[cfg(feature = "olap")] pub report_download_config: ReportConfig, #[cfg(feature = "olap")] - pub opensearch: OpensearchConfig, + pub opensearch: OpenSearchConfig, pub events: EventsConfig, #[cfg(feature = "olap")] pub connector_onboarding: SecretStateContainer, @@ -730,6 +730,9 @@ impl Settings { self.lock_settings.validate()?; self.events.validate()?; + #[cfg(feature = "olap")] + self.opensearch.validate()?; + self.encryption_management .validate() .map_err(|err| ApplicationError::InvalidConfigurationValueError(err.into()))?; diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 481eba0df702..85e650390f8a 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -106,6 +106,7 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::PayBright | PaymentMethodType::Sepa | PaymentMethodType::Vipps + | PaymentMethodType::Venmo | PaymentMethodType::Paypal => match capture_method { enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index ec55a0be1c86..6dfe899f2d1c 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -4674,6 +4674,12 @@ impl TryFrom<&AdyenRouterData<&types::PayoutsRouterData>> for AdyenPayoutC }, )?, }, + api_models::payouts::Wallet::Venmo(_) => { + Err(errors::ConnectorError::NotSupported { + message: "Venmo Wallet is not supported".to_string(), + connector: "Adyen", + })? + } }; let address: &payments::AddressDetails = item.router_data.get_billing_address()?; let payout_wallet = PayoutWalletData { diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index 3ea736486ef0..efece7f61fb2 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -378,11 +378,34 @@ impl let error_response = get_error_response_if_failure((&info_response, mandate_status, item.http_code)); - let connector_response = info_response - .processor_information - .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => info_response + .processor_information + .as_ref() + .and_then(|processor_information| { + info_response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + types::AdditionalPaymentMethodConnectorResponse::from(( + processor_information, + consumer_auth_information, + )) + }) + }) + .map(types::ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::GiftCard => None, + }; Ok(Self { status: mandate_status, @@ -722,6 +745,44 @@ pub struct ClientReferenceInformation { pub struct ClientProcessorInformation { avs: Option, card_verification: Option, + processor: Option, + network_transaction_id: Option>, + approval_code: Option, + merchant_advice: Option, + response_code: Option, + ach_verification: Option, + system_trace_audit_number: Option, + event_status: Option, + retrieval_reference_number: Option, + consumer_authentication_response: Option, + response_details: Option, + transaction_id: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantAdvice { + code: Option, + code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsumerAuthenticationResponse { + code: Option, + code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AchVerification { + result_code_raw: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessorResponse { + name: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -735,6 +796,39 @@ pub struct CardVerification { #[serde(rename_all = "camelCase")] pub struct ClientRiskInformation { rules: Option>, + profile: Option, + score: Option, + info_codes: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InfoCodes { + address: Option>, + identity_change: Option>, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Score { + factor_codes: Option>, + result: Option, + model_used: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(untagged)] +pub enum RiskResult { + StringVariant(String), + IntVariant(u64), +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Profile { + early_decision: Option, + name: Option, + decision: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1254,26 +1348,36 @@ pub struct ClientAuthSetupInfoResponse { id: String, client_reference_information: ClientReferenceInformation, consumer_authentication_information: BankOfAmericaConsumerAuthInformationResponse, + processor_information: Option, + processing_information: Option, + payment_account_information: Option, + payment_information: Option, + payment_insights_information: Option, + risk_information: Option, + token_information: Option, + error_information: Option, + issuer_information: Option, + reconciliation_id: Option, } #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum BankOfAmericaAuthSetupResponse { - ClientAuthSetupInfo(ClientAuthSetupInfoResponse), + ClientAuthSetupInfo(Box), ErrorInformation(BankOfAmericaErrorInformationResponse), } #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum BankOfAmericaPaymentsResponse { - ClientReferenceInformation(BankOfAmericaClientReferenceResponse), + ClientReferenceInformation(Box), ErrorInformation(BankOfAmericaErrorInformationResponse), } #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum BankOfAmericaSetupMandatesResponse { - ClientReferenceInformation(BankOfAmericaClientReferenceResponse), + ClientReferenceInformation(Box), ErrorInformation(BankOfAmericaErrorInformationResponse), } @@ -1284,9 +1388,125 @@ pub struct BankOfAmericaClientReferenceResponse { status: BankofamericaPaymentStatus, client_reference_information: ClientReferenceInformation, processor_information: Option, + processing_information: Option, + payment_information: Option, + payment_insights_information: Option, risk_information: Option, token_information: Option, error_information: Option, + issuer_information: Option, + sender_information: Option, + payment_account_information: Option, + reconciliation_id: Option, + consumer_authentication_information: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ConsumerAuthenticationInformation { + eci_raw: Option, + eci: Option, + acs_transaction_id: Option, + cavv: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct SenderInformation { + payment_information: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentInsightsInformation { + response_insights: Option, + rule_results: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ResponseInsights { + category_code: Option, + category: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct RuleResults { + id: Option, + decision: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentInformationResponse { + tokenized_card: Option, + customer: Option, + card: Option, + scheme: Option, + bin: Option, + account_type: Option, + issuer: Option, + bin_country: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CustomerResponseObject { + customer_id: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountInformation { + card: Option, + features: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountFeatureInformation { + health_card: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct PaymentAccountCardInformation { + #[serde(rename = "type")] + card_type: Option, + hashed_number: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ProcessingInformationResponse { + payment_solution: Option, + commerce_indicator: Option, + commerce_indicator_label: Option, + authorization_options: Option, + ecommerce_indicator: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct AuthorizationOptions { + auth_type: Option, + initiator: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct Initiator { + merchant_initiated_transaction: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MerchantInitiatedTransactionResponse { + agreement_id: Option, + previous_transaction_id: Option, + original_authorized_amount: Option, + reason: Option, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -1295,6 +1515,27 @@ pub struct BankOfAmericaTokenInformation { payment_instrument: Option, } +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct IssuerInformation { + country: Option, + discretionary_data: Option, + country_specific_discretionary_data: Option, + response_code: Option, + pin_request_indicator: Option, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct CardResponseObject { + suffix: Option, + prefix: Option, + expiration_month: Option>, + expiration_year: Option>, + #[serde(rename = "type")] + card_type: Option, +} + #[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaErrorInformationResponse { @@ -1862,11 +2103,35 @@ impl || is_setup_mandate_payment(&item.data.request), )); let response = get_payment_response((&info_response, status, item.http_code)); - let connector_response = info_response - .processor_information - .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => info_response + .processor_information + .as_ref() + .and_then(|processor_information| { + info_response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + types::AdditionalPaymentMethodConnectorResponse::from(( + processor_information, + consumer_auth_information, + )) + }) + }) + .map(types::ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::GiftCard => None, + }; + Ok(Self { status, response, @@ -1911,11 +2176,34 @@ impl item.data.request.is_auto_capture()?, )); let response = get_payment_response((&info_response, status, item.http_code)); - let connector_response = info_response - .processor_information - .as_ref() - .map(types::AdditionalPaymentMethodConnectorResponse::from) - .map(types::ConnectorResponseData::with_additional_payment_method_data); + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => info_response + .processor_information + .as_ref() + .and_then(|processor_information| { + info_response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + types::AdditionalPaymentMethodConnectorResponse::from(( + processor_information, + consumer_auth_information, + )) + }) + }) + .map(types::ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::GiftCard => None, + }; Ok(Self { status, @@ -1935,14 +2223,38 @@ impl } } -impl From<&ClientProcessorInformation> for types::AdditionalPaymentMethodConnectorResponse { - fn from(processor_information: &ClientProcessorInformation) -> Self { - let payment_checks = Some( - serde_json::json!({"avs_response": processor_information.avs, "card_verification": processor_information.card_verification}), - ); +impl + From<( + &ClientProcessorInformation, + &ConsumerAuthenticationInformation, + )> for types::AdditionalPaymentMethodConnectorResponse +{ + fn from( + item: ( + &ClientProcessorInformation, + &ConsumerAuthenticationInformation, + ), + ) -> Self { + let processor_information = item.0; + let consumer_authentication_information = item.1; + let payment_checks = Some(serde_json::json!({ + "avs_response": processor_information.avs, + "card_verification": processor_information.card_verification, + "approval_code": processor_information.approval_code, + "consumer_authentication_response": processor_information.consumer_authentication_response, + "cavv": consumer_authentication_information.cavv, + "eci": consumer_authentication_information.eci, + "eci_raw": consumer_authentication_information.eci_raw, + })); + + let authentication_data = Some(serde_json::json!({ + "retrieval_reference_number": processor_information.retrieval_reference_number, + "acs_transaction_id": consumer_authentication_information.acs_transaction_id, + "system_trace_audit_number": processor_information.system_trace_audit_number, + })); Self::Card { - authentication_data: None, + authentication_data, payment_checks, } } @@ -2025,7 +2337,7 @@ impl #[derive(Debug, Deserialize, Serialize)] #[serde(untagged)] pub enum BankOfAmericaTransactionResponse { - ApplicationInformation(BankOfAmericaApplicationInfoResponse), + ApplicationInformation(Box), ErrorInformation(BankOfAmericaErrorInformationResponse), } @@ -2035,7 +2347,22 @@ pub struct BankOfAmericaApplicationInfoResponse { id: String, application_information: ApplicationInformation, client_reference_information: Option, + processor_information: Option, + processing_information: Option, + payment_information: Option, + payment_insights_information: Option, error_information: Option, + fraud_marking_information: Option, + risk_information: Option, + token_information: Option, + reconciliation_id: Option, + consumer_authentication_information: Option, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct FraudMarkingInformation { + reason: Option, } #[derive(Debug, Deserialize, Serialize)] @@ -2069,6 +2396,36 @@ impl app_response.application_information.status, item.data.request.is_auto_capture()?, )); + + let connector_response = match item.data.payment_method { + common_enums::PaymentMethod::Card => app_response + .processor_information + .as_ref() + .and_then(|processor_information| { + app_response + .consumer_authentication_information + .as_ref() + .map(|consumer_auth_information| { + types::AdditionalPaymentMethodConnectorResponse::from(( + processor_information, + consumer_auth_information, + )) + }) + }) + .map(types::ConnectorResponseData::with_additional_payment_method_data), + common_enums::PaymentMethod::CardRedirect + | common_enums::PaymentMethod::PayLater + | common_enums::PaymentMethod::Wallet + | common_enums::PaymentMethod::BankRedirect + | common_enums::PaymentMethod::BankTransfer + | common_enums::PaymentMethod::Crypto + | common_enums::PaymentMethod::BankDebit + | common_enums::PaymentMethod::Reward + | common_enums::PaymentMethod::Upi + | common_enums::PaymentMethod::Voucher + | common_enums::PaymentMethod::GiftCard => None, + }; + let risk_info: Option = None; if utils::is_payment_failure(status) { Ok(Self { @@ -2079,6 +2436,7 @@ impl app_response.id.clone(), ))), status: enums::AttemptStatus::Failure, + connector_response, ..item.data }) } else { @@ -2098,6 +2456,7 @@ impl .unwrap_or(Some(app_response.id)), incremental_authorization_allowed: None, }), + connector_response, ..item.data }) } diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 814bd68da688..5c174c69e3b1 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -398,6 +398,7 @@ impl | common_enums::PaymentMethodType::Trustly | common_enums::PaymentMethodType::Twint | common_enums::PaymentMethodType::UpiCollect + | common_enums::PaymentMethodType::Venmo | common_enums::PaymentMethodType::Vipps | common_enums::PaymentMethodType::Walley | common_enums::PaymentMethodType::WeChatPay diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index a240154a0f38..8fd148864f55 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -373,11 +373,13 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for NoonPaymentsRequest { .get_setup_mandate_details() .map(|mandate_data| { let max_amount = match &mandate_data.mandate_type { - Some(data_models::mandates::MandateDataType::SingleUse(mandate)) - | Some(data_models::mandates::MandateDataType::MultiUse(Some(mandate))) => { - conn_utils::to_currency_base_unit(mandate.amount, mandate.currency) - } - Some(data_models::mandates::MandateDataType::MultiUse(None)) => { + Some(hyperswitch_domain_models::mandates::MandateDataType::SingleUse( + mandate, + )) + | Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some( + mandate, + ))) => conn_utils::to_currency_base_unit(mandate.amount, mandate.currency), + Some(hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None)) => { Err(errors::ConnectorError::MissingRequiredField { field_name: "setup_future_usage.mandate_data.mandate_type.multi_use.amount", diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index 793ec2d87dbc..e0d7a58566d6 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -5,8 +5,8 @@ use common_utils::{ fp_utils, pii::{Email, IpAddress}, }; -use data_models::mandates::MandateDataType; use error_stack::ResultExt; +use hyperswitch_domain_models::mandates::MandateDataType; use masking::{ExposeInterface, PeekInterface, Secret}; use reqwest::Url; use serde::{Deserialize, Serialize}; diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index e9e986b66fd6..63912391787d 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -6,6 +6,8 @@ use common_utils::{ext_traits::ByteSliceExt, request::RequestContent}; use diesel_models::enums; use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret}; +#[cfg(feature = "payouts")] +use router_env::{instrument, tracing}; use transformers as paypal; use self::transformers::{auth_headers, PaypalAuthResponse, PaypalMeta, PaypalWebhookEventType}; @@ -56,6 +58,12 @@ impl api::RefundExecute for Paypal {} impl api::RefundSync for Paypal {} impl api::ConnectorVerifyWebhookSource for Paypal {} +impl api::Payouts for Paypal {} +#[cfg(feature = "payouts")] +impl api::PayoutCreate for Paypal {} +#[cfg(feature = "payouts")] +impl api::PayoutFulfill for Paypal {} + impl Paypal { pub fn get_order_error_response( &self, @@ -425,6 +433,110 @@ impl ConnectorIntegration + for Paypal +{ + fn get_url( + &self, + _req: &types::PayoutsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}v1/payments/payouts", self.base_url(connectors))) + } + + fn get_headers( + &self, + req: &types::PayoutsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_request_body( + &self, + req: &types::PayoutsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = paypal::PaypalRouterData::try_from(( + &self.get_currency_unit(), + req.request.destination_currency, + req.request.amount, + req, + ))?; + let connector_req = paypal::PaypalFulfillRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PayoutsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PayoutFulfillType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PayoutFulfillType::get_headers( + self, req, connectors, + )?) + .set_body(types::PayoutFulfillType::get_request_body( + self, req, connectors, + )?) + .build(); + + Ok(Some(request)) + } + + #[instrument(skip_all)] + fn handle_response( + &self, + data: &types::PayoutsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: paypal::PaypalFulfillResponse = res + .response + .parse_struct("PaypalFulfillResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +#[cfg(feature = "payouts")] +impl ConnectorIntegration + for Paypal +{ + fn build_request( + &self, + _req: &types::PayoutsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + // Eligibility check for wallet is not implemented + Err( + errors::ConnectorError::NotImplemented("Payout Eligibility for Paypal".to_string()) + .into(), + ) + } +} + impl ConnectorIntegration< api::SetupMandate, diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index f136ba878366..21b0f04af86b 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -1,6 +1,8 @@ use api_models::enums; use base64::Engine; use common_utils::errors::CustomResult; +#[cfg(feature = "payouts")] +use common_utils::pii::Email; use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; @@ -1468,6 +1470,212 @@ impl } } +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +pub struct PaypalFulfillRequest { + sender_batch_header: PayoutBatchHeader, + items: Vec, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +pub struct PayoutBatchHeader { + sender_batch_id: String, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +pub struct PaypalPayoutItem { + amount: PayoutAmount, + note: Option, + notification_language: String, + #[serde(flatten)] + payout_method_data: PaypalPayoutMethodData, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +pub struct PaypalPayoutMethodData { + recipient_type: PayoutRecipientType, + recipient_wallet: PayoutWalletType, + receiver: PaypalPayoutDataType, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayoutRecipientType { + Email, + PaypalId, + Phone, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PayoutWalletType { + Paypal, + Venmo, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +#[serde(untagged)] +pub enum PaypalPayoutDataType { + EmailType(Email), + OtherType(Secret), +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Serialize)] +pub struct PayoutAmount { + value: String, + currency: storage_enums::Currency, +} + +#[cfg(feature = "payouts")] +impl TryFrom<&PaypalRouterData<&types::PayoutsRouterData>> + for PaypalFulfillRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::PayoutsRouterData>, + ) -> Result { + let item_data = PaypalPayoutItem::try_from(item)?; + Ok(Self { + sender_batch_header: PayoutBatchHeader { + sender_batch_id: item.router_data.request.payout_id.to_owned(), + }, + items: vec![item_data], + }) + } +} + +#[cfg(feature = "payouts")] +impl TryFrom<&PaypalRouterData<&types::PayoutsRouterData>> for PaypalPayoutItem { + type Error = error_stack::Report; + fn try_from( + item: &PaypalRouterData<&types::PayoutsRouterData>, + ) -> Result { + let amount = PayoutAmount { + value: item.amount.to_owned(), + currency: item.router_data.request.destination_currency, + }; + + let payout_method_data = match item.router_data.get_payout_method_data()? { + api::PayoutMethodData::Wallet(wallet_data) => match wallet_data { + api::WalletPayout::Paypal(data) => { + let (recipient_type, receiver) = + match (data.email, data.telephone_number, data.paypal_id) { + (Some(email), _, _) => ( + PayoutRecipientType::Email, + PaypalPayoutDataType::EmailType(email), + ), + (_, Some(phone), _) => ( + PayoutRecipientType::Phone, + PaypalPayoutDataType::OtherType(phone), + ), + (_, _, Some(paypal_id)) => ( + PayoutRecipientType::PaypalId, + PaypalPayoutDataType::OtherType(paypal_id), + ), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "receiver_data", + })?, + }; + + PaypalPayoutMethodData { + recipient_type, + recipient_wallet: PayoutWalletType::Paypal, + receiver, + } + } + api::WalletPayout::Venmo(data) => { + let receiver = PaypalPayoutDataType::OtherType(data.telephone_number.ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "telephone_number", + }, + )?); + PaypalPayoutMethodData { + recipient_type: PayoutRecipientType::Phone, + recipient_wallet: PayoutWalletType::Venmo, + receiver, + } + } + }, + _ => Err(errors::ConnectorError::NotSupported { + message: "PayoutMethodType is not supported".to_string(), + connector: "Paypal", + })?, + }; + + Ok(Self { + amount, + payout_method_data, + note: item.router_data.description.to_owned(), + notification_language: consts::DEFAULT_NOTIFICATION_SCRIPT_LANGUAGE.to_string(), + }) + } +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalFulfillResponse { + batch_header: PaypalBatchResponse, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Deserialize, Serialize)] +pub struct PaypalBatchResponse { + payout_batch_id: String, + batch_status: PaypalFulfillStatus, +} + +#[cfg(feature = "payouts")] +#[derive(Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaypalFulfillStatus { + Denied, + Pending, + Processing, + Success, + Cancelled, +} + +#[cfg(feature = "payouts")] +impl ForeignFrom for storage_enums::PayoutStatus { + fn foreign_from(status: PaypalFulfillStatus) -> Self { + match status { + PaypalFulfillStatus::Success => Self::Success, + PaypalFulfillStatus::Denied => Self::Failed, + PaypalFulfillStatus::Cancelled => Self::Cancelled, + PaypalFulfillStatus::Pending | PaypalFulfillStatus::Processing => Self::Pending, + } + } +} + +#[cfg(feature = "payouts")] +impl TryFrom> + for types::PayoutsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::PayoutsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::PayoutsResponseData { + status: Some(storage_enums::PayoutStatus::foreign_from( + item.response.batch_header.batch_status, + )), + connector_payout_id: item.response.batch_header.payout_batch_id, + payout_eligible: None, + should_add_next_step_to_process_tracker: false, + }), + ..item.data + }) + } +} + #[derive(Debug, Serialize)] pub struct PaypalPaymentsCaptureRequest { amount: OrderAmount, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index c561b26bcfd1..46c47a51f327 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -7,8 +7,8 @@ use common_utils::{ pii::{self, Email}, request::RequestContent, }; -use data_models::mandates::AcceptanceType; use error_stack::ResultExt; +use hyperswitch_domain_models::mandates::AcceptanceType; use masking::{ExposeInterface, ExposeOptionInterface, PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use serde_json::Value; @@ -702,6 +702,7 @@ impl TryFrom for StripePaymentMethodType { | enums::PaymentMethodType::Trustly | enums::PaymentMethodType::Twint | enums::PaymentMethodType::Vipps + | enums::PaymentMethodType::Venmo | enums::PaymentMethodType::Alfamart | enums::PaymentMethodType::BcaBankTransfer | enums::PaymentMethodType::BniVa diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index efcaa5bfce77..2cb3aa436e45 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -12,9 +12,9 @@ use common_utils::{ errors::ReportSwitchExt, pii::{self, Email, IpAddress}, }; -use data_models::payments::payment_attempt::PaymentAttempt; use diesel_models::enums; use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; use masking::{ExposeInterface, Secret}; use once_cell::sync::Lazy; use regex::Regex; diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 2c4ec3f5e70b..c40d284ee3ac 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -38,6 +38,8 @@ pub(crate) const LOW_BALANCE_ERROR_MESSAGE: &str = "Insufficient balance in the pub(crate) const CONNECTOR_UNAUTHORIZED_ERROR: &str = "Authentication Error from the connector"; pub(crate) const CANNOT_CONTINUE_AUTH: &str = "Cannot continue with Authorization due to failed Liability Shift."; +#[cfg(feature = "payouts")] +pub(crate) const DEFAULT_NOTIFICATION_SCRIPT_LANGUAGE: &str = "en-US"; // General purpose base64 engines pub(crate) const BASE64_ENGINE: base64::engine::GeneralPurpose = diff --git a/crates/router/src/consts/user.rs b/crates/router/src/consts/user.rs index 1cda969f780e..f14610649f43 100644 --- a/crates/router/src/consts/user.rs +++ b/crates/router/src/consts/user.rs @@ -1,3 +1,5 @@ pub const MAX_NAME_LENGTH: usize = 70; pub const MAX_COMPANY_NAME_LENGTH: usize = 70; pub const BUSINESS_EMAIL: &str = "biz@hyperswitch.io"; +pub const MAX_PASSWORD_LENGTH: usize = 70; +pub const MIN_PASSWORD_LENGTH: usize = 8; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index a7d36ff5128b..d80d86460b2e 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -10,8 +10,8 @@ use std::fmt::Display; use actix_web::{body::BoxBody, ResponseError}; pub use common_utils::errors::{CustomResult, ParsingError, ValidationError}; -pub use data_models::errors::StorageError as DataStorageError; use diesel_models::errors as storage_errors; +pub use hyperswitch_domain_models::errors::StorageError as DataStorageError; pub use redis_interface::errors::RedisError; use scheduler::errors as sch_errors; use storage_impl::errors as storage_impl_errors; diff --git a/crates/router/src/core/errors/user/sample_data.rs b/crates/router/src/core/errors/user/sample_data.rs index 84c6c9fa43a2..3e72e49d317a 100644 --- a/crates/router/src/core/errors/user/sample_data.rs +++ b/crates/router/src/core/errors/user/sample_data.rs @@ -1,6 +1,6 @@ use api_models::errors::types::{ApiError, ApiErrorResponse}; use common_utils::errors::{CustomResult, ErrorSwitch, ErrorSwitchFrom}; -use data_models::errors::StorageError; +use hyperswitch_domain_models::errors::StorageError; pub type SampleDataResult = CustomResult; diff --git a/crates/router/src/core/errors/utils.rs b/crates/router/src/core/errors/utils.rs index f664c33681cd..678a351a4c31 100644 --- a/crates/router/src/core/errors/utils.rs +++ b/crates/router/src/core/errors/utils.rs @@ -42,7 +42,7 @@ impl StorageErrorExt } impl StorageErrorExt - for error_stack::Result + for error_stack::Result { #[track_caller] fn to_not_found_response( @@ -51,8 +51,10 @@ impl StorageErrorExt ) -> error_stack::Result { self.map_err(|err| { let new_err = match err.current_context() { - data_models::errors::StorageError::ValueNotFound(_) => not_found_response, - data_models::errors::StorageError::CustomerRedacted => { + hyperswitch_domain_models::errors::StorageError::ValueNotFound(_) => { + not_found_response + } + hyperswitch_domain_models::errors::StorageError::CustomerRedacted => { errors::ApiErrorResponse::CustomerRedacted } _ => errors::ApiErrorResponse::InternalServerError, @@ -68,7 +70,9 @@ impl StorageErrorExt ) -> error_stack::Result { self.map_err(|err| { let new_err = match err.current_context() { - data_models::errors::StorageError::DuplicateValue { .. } => duplicate_response, + hyperswitch_domain_models::errors::StorageError::DuplicateValue { .. } => { + duplicate_response + } _ => errors::ApiErrorResponse::InternalServerError, }; err.change_context(new_err) diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs index d59c8c5ff470..3a80865bcd0e 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_post.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_post.rs @@ -2,7 +2,7 @@ use api_models::payments::HeaderPayload; use async_trait::async_trait; use common_enums::{CaptureMethod, FrmSuggestion}; use common_utils::ext_traits::Encode; -use data_models::payments::{ +use hyperswitch_domain_models::payments::{ payment_attempt::PaymentAttemptUpdate, payment_intent::PaymentIntentUpdate, }; use router_env::{instrument, logger, tracing}; diff --git a/crates/router/src/core/fraud_check/types.rs b/crates/router/src/core/fraud_check/types.rs index 082210a2e473..5acd722077fb 100644 --- a/crates/router/src/core/fraud_check/types.rs +++ b/crates/router/src/core/fraud_check/types.rs @@ -6,7 +6,7 @@ use api_models::{ }; use common_enums::FrmSuggestion; use common_utils::pii::Email; -use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; +use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use masking::Serialize; use serde::Deserialize; use utoipa::ToSchema; diff --git a/crates/router/src/core/health_check.rs b/crates/router/src/core/health_check.rs index 9997c6261fd6..e90fe77e8087 100644 --- a/crates/router/src/core/health_check.rs +++ b/crates/router/src/core/health_check.rs @@ -23,6 +23,11 @@ pub trait HealthCheckInterface { #[cfg(feature = "olap")] async fn health_check_analytics(&self) -> CustomResult; + + #[cfg(feature = "olap")] + async fn health_check_opensearch( + &self, + ) -> CustomResult; } #[async_trait::async_trait] @@ -122,6 +127,18 @@ impl HealthCheckInterface for app::AppState { Ok(HealthState::Running) } + #[cfg(feature = "olap")] + async fn health_check_opensearch( + &self, + ) -> CustomResult { + self.opensearch_client + .deep_health_check() + .await + .change_context(errors::HealthCheckDBError::OpensearchError)?; + + Ok(HealthState::Running) + } + async fn health_check_outgoing( &self, ) -> CustomResult { diff --git a/crates/router/src/core/mandate.rs b/crates/router/src/core/mandate.rs index 84aa9b48e6fc..939367bd4db8 100644 --- a/crates/router/src/core/mandate.rs +++ b/crates/router/src/core/mandate.rs @@ -515,6 +515,8 @@ pub trait MandateBehaviour { fn get_mandate_id(&self) -> Option<&api_models::payments::MandateIds>; fn set_mandate_id(&mut self, new_mandate_id: Option); fn get_payment_method_data(&self) -> domain::payments::PaymentMethodData; - fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData>; + fn get_setup_mandate_details( + &self, + ) -> Option<&hyperswitch_domain_models::mandates::MandateData>; fn get_customer_acceptance(&self) -> Option; } diff --git a/crates/router/src/core/mandate/helpers.rs b/crates/router/src/core/mandate/helpers.rs index c15969fd1c8c..f3b7b384e2d5 100644 --- a/crates/router/src/core/mandate/helpers.rs +++ b/crates/router/src/core/mandate/helpers.rs @@ -1,9 +1,9 @@ use api_models::payments as api_payments; use common_enums::enums; use common_utils::errors::CustomResult; -use data_models::mandates::MandateData; use diesel_models::Mandate; use error_stack::ResultExt; +use hyperswitch_domain_models::mandates::MandateData; use crate::{ core::{errors, payments}, diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 2b7577e1fb8e..b2a4959edfbc 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -7,8 +7,8 @@ pub use api_models::enums::Connector; use api_models::payments::CardToken; #[cfg(feature = "payouts")] pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; -use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use diesel_models::enums; +use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use crate::{ core::{errors::RouterResult, payments::helpers, pm_auth as core_pm_auth}, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 42145550c893..27d126bce909 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -2504,7 +2504,7 @@ pub async fn list_payment_methods( payment_methods: payment_method_responses, mandate_payment: payment_attempt.and_then(|inner| inner.mandate_details).map( |d| match d { - data_models::mandates::MandateDataType::SingleUse(i) => { + hyperswitch_domain_models::mandates::MandateDataType::SingleUse(i) => { api::MandateType::SingleUse(api::MandateAmountData { amount: i.amount, currency: i.currency, @@ -2513,7 +2513,7 @@ pub async fn list_payment_methods( metadata: i.metadata, }) } - data_models::mandates::MandateDataType::MultiUse(Some(i)) => { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some(i)) => { api::MandateType::MultiUse(Some(api::MandateAmountData { amount: i.amount, currency: i.currency, @@ -2522,7 +2522,7 @@ pub async fn list_payment_methods( metadata: i.metadata, })) } - data_models::mandates::MandateDataType::MultiUse(None) => { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None) => { api::MandateType::MultiUse(None) } }, @@ -3190,7 +3190,7 @@ pub async fn do_list_customer_pm_fetch_customer_if_not_passed( .await } else { let cloned_secret = req.and_then(|r| r.client_secret.as_ref().cloned()); - let payment_intent: Option = + let payment_intent: Option = helpers::verify_payment_intent_time_and_client_secret( db, &merchant_account, diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index fafff7253a29..6d4f2322a8d8 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1,3 +1,4 @@ +use common_enums::PaymentMethodType; use common_utils::{ crypto::{DecodeMessage, EncodeMessage, GcmAes256}, ext_traits::{BytesExt, Encode}, @@ -419,6 +420,9 @@ impl Vaultable for api::CardPayout { #[derive(Debug, serde::Serialize, serde::Deserialize)] pub struct TokenizedWalletSensitiveValues { pub email: Option, + pub telephone_number: Option>, + pub wallet_id: Option>, + pub wallet_type: PaymentMethodType, } #[derive(Debug, serde::Serialize, serde::Deserialize)] @@ -432,6 +436,15 @@ impl Vaultable for api::WalletPayout { let value1 = match self { Self::Paypal(paypal_data) => TokenizedWalletSensitiveValues { email: paypal_data.email.clone(), + telephone_number: paypal_data.telephone_number.clone(), + wallet_id: paypal_data.paypal_id.clone(), + wallet_type: PaymentMethodType::Paypal, + }, + Self::Venmo(venmo_data) => TokenizedWalletSensitiveValues { + email: None, + telephone_number: venmo_data.telephone_number.clone(), + wallet_id: None, + wallet_type: PaymentMethodType::Venmo, }, }; @@ -464,9 +477,17 @@ impl Vaultable for api::WalletPayout { .change_context(errors::VaultError::ResponseDeserializationFailed) .attach_printable("Could not deserialize into wallet data wallet_insensitive_data")?; - let wallet = Self::Paypal(api_models::payouts::Paypal { - email: value1.email, - }); + let wallet = match value1.wallet_type { + PaymentMethodType::Paypal => Self::Paypal(api_models::payouts::Paypal { + email: value1.email, + telephone_number: value1.telephone_number, + paypal_id: value1.wallet_id, + }), + PaymentMethodType::Venmo => Self::Venmo(api_models::payouts::Venmo { + telephone_number: value1.telephone_number, + }), + _ => Err(errors::VaultError::PayoutMethodNotSupported)?, + }; let supp_data = SupplementaryVaultData { customer_id: value2.customer_id, payment_method_id: None, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e2a5f1484408..815c604a0e3f 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -27,12 +27,12 @@ use common_utils::{ pii, types::Surcharge, }; -use data_models::mandates::{CustomerAcceptance, MandateData}; use diesel_models::{ephemeral_key, fraud_check::FraudCheck}; use error_stack::{report, ResultExt}; use events::EventInfo; use futures::future::join_all; use helpers::ApplePayData; +use hyperswitch_domain_models::mandates::{CustomerAcceptance, MandateData}; use masking::{ExposeInterface, Secret}; pub use payment_address::PaymentAddress; use redis_interface::errors::RedisError; @@ -527,6 +527,16 @@ where let cloned_payment_data = payment_data.clone(); let cloned_customer = customer.clone(); + operation + .to_domain()? + .store_extended_card_info_temporarily( + state, + &payment_data.payment_intent.payment_id, + &business_profile, + &payment_data.payment_method_data, + ) + .await?; + crate::utils::trigger_payments_webhook( merchant_account, business_profile, @@ -2703,7 +2713,7 @@ pub async fn list_payments( merchant: domain::MerchantAccount, constraints: api::PaymentListConstraints, ) -> RouterResponse { - use data_models::errors::StorageError; + use hyperswitch_domain_models::errors::StorageError; helpers::validate_payment_list_request(&constraints)?; let merchant_id = &merchant.merchant_id; let db = state.store.as_ref(); diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index c96f6f2c144e..c7ec86ac5e6a 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -997,7 +997,6 @@ default_imp_for_payouts!( connector::Opennode, connector::Payeezy, connector::Payme, - connector::Paypal, connector::Payu, connector::Placetopay, connector::Powertranz, @@ -1082,7 +1081,6 @@ default_imp_for_payouts_create!( connector::Opennode, connector::Payeezy, connector::Payme, - connector::Paypal, connector::Payu, connector::Placetopay, connector::Powertranz, @@ -1256,7 +1254,6 @@ default_imp_for_payouts_fulfill!( connector::Opennode, connector::Payeezy, connector::Payme, - connector::Paypal, connector::Payu, connector::Placetopay, connector::Powertranz, diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index e44456fb1c7b..6b64dc040445 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -231,7 +231,9 @@ impl mandate::MandateBehaviour for types::PaymentsAuthorizeData { fn get_setup_future_usage(&self) -> Option { self.setup_future_usage } - fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData> { + fn get_setup_mandate_details( + &self, + ) -> Option<&hyperswitch_domain_models::mandates::MandateData> { self.setup_mandate_details.as_ref() } diff --git a/crates/router/src/core/payments/flows/setup_mandate_flow.rs b/crates/router/src/core/payments/flows/setup_mandate_flow.rs index 6a810e9b3dda..59f7ee17551a 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -177,7 +177,9 @@ impl mandate::MandateBehaviour for types::SetupMandateRequestData { self.payment_method_data.clone() } - fn get_setup_mandate_details(&self) -> Option<&data_models::mandates::MandateData> { + fn get_setup_mandate_details( + &self, + ) -> Option<&hyperswitch_domain_models::mandates::MandateData> { self.setup_mandate_details.as_ref() } fn get_customer_acceptance(&self) -> Option { diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 50207b5627a4..d2a2896de8c0 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -9,13 +9,13 @@ use common_utils::{ ext_traits::{AsyncExt, ByteSliceExt, Encode, ValueExt}, fp_utils, generate_id, pii, }; -use data_models::{ - mandates::MandateData, - payments::{payment_attempt::PaymentAttempt, PaymentIntent}, -}; use diesel_models::enums; // TODO : Evaluate all the helper functions () use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + mandates::MandateData, + payments::{payment_attempt::PaymentAttempt, PaymentIntent}, +}; use josekit::jwe; use masking::{ExposeInterface, PeekInterface}; use openssl::{ @@ -1159,7 +1159,7 @@ pub fn verify_mandate_details_for_recurring_payments( #[instrument(skip_all)] pub fn payment_attempt_status_fsm( - payment_method_data: &Option, + payment_method_data: Option<&api::payments::PaymentMethodData>, confirm: Option, ) -> storage_enums::AttemptStatus { match payment_method_data { @@ -1172,7 +1172,7 @@ pub fn payment_attempt_status_fsm( } pub fn payment_intent_status_fsm( - payment_method_data: &Option, + payment_method_data: Option<&api::PaymentMethodData>, confirm: Option, ) -> storage_enums::IntentStatus { match payment_method_data { @@ -2129,8 +2129,14 @@ pub(crate) fn validate_amount_to_capture( pub(crate) fn validate_payment_method_fields_present( req: &api::PaymentsRequest, ) -> RouterResult<()> { + let payment_method_data = + req.payment_method_data + .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }); utils::when( - req.payment_method.is_none() && req.payment_method_data.is_some(), + req.payment_method.is_none() && payment_method_data.is_some(), || { Err(errors::ApiErrorResponse::MissingRequiredField { field_name: "payment_method", @@ -2152,7 +2158,7 @@ pub(crate) fn validate_payment_method_fields_present( utils::when( req.payment_method.is_some() - && req.payment_method_data.is_none() + && payment_method_data.is_none() && req.payment_token.is_none() && req.recurring_details.is_none(), || { @@ -2194,14 +2200,14 @@ pub(crate) fn validate_payment_method_fields_present( }; utils::when( - req.payment_method.is_some() && req.payment_method_data.is_some(), + req.payment_method.is_some() && payment_method_data.is_some(), || { - req.payment_method_data - .clone() - .map_or(Ok(()), |req_payment_method_data| { + payment_method_data + .cloned() + .map_or(Ok(()), |payment_method_data| { req.payment_method.map_or(Ok(()), |req_payment_method| { validate_payment_method_and_payment_method_data( - req_payment_method_data.payment_method_data, + payment_method_data, req_payment_method, ) }) @@ -2672,24 +2678,28 @@ pub fn generate_mandate( Ok(Some( match data.mandate_type.get_required_value("mandate_type")? { - data_models::mandates::MandateDataType::SingleUse(data) => new_mandate - .set_mandate_amount(Some(data.amount)) - .set_mandate_currency(Some(data.currency)) - .set_mandate_type(storage_enums::MandateType::SingleUse) - .to_owned(), - - data_models::mandates::MandateDataType::MultiUse(op_data) => match op_data { - Some(data) => new_mandate + hyperswitch_domain_models::mandates::MandateDataType::SingleUse(data) => { + new_mandate .set_mandate_amount(Some(data.amount)) .set_mandate_currency(Some(data.currency)) - .set_start_date(data.start_date) - .set_end_date(data.end_date), - // .set_metadata(data.metadata), - // we are storing PaymentMethodData in metadata of mandate - None => &mut new_mandate, + .set_mandate_type(storage_enums::MandateType::SingleUse) + .to_owned() + } + + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(op_data) => { + match op_data { + Some(data) => new_mandate + .set_mandate_amount(Some(data.amount)) + .set_mandate_currency(Some(data.currency)) + .set_start_date(data.start_date) + .set_end_date(data.end_date), + // .set_metadata(data.metadata), + // we are storing PaymentMethodData in metadata of mandate + None => &mut new_mandate, + } + .set_mandate_type(storage_enums::MandateType::MultiUse) + .to_owned() } - .set_mandate_type(storage_enums::MandateType::MultiUse) - .to_owned(), }, )) } @@ -2911,7 +2921,9 @@ mod tests { fingerprint_id: None, off_session: None, client_secret: Some("1".to_string()), - active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()), + active_attempt: hyperswitch_domain_models::RemoteStorageObject::ForeignID( + "nopes".to_string(), + ), business_country: None, business_label: None, order_details: None, @@ -2967,7 +2979,9 @@ mod tests { setup_future_usage: None, off_session: None, client_secret: Some("1".to_string()), - active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()), + active_attempt: hyperswitch_domain_models::RemoteStorageObject::ForeignID( + "nopes".to_string(), + ), business_country: None, business_label: None, order_details: None, @@ -3022,7 +3036,9 @@ mod tests { off_session: None, client_secret: None, fingerprint_id: None, - active_attempt: data_models::RemoteStorageObject::ForeignID("nopes".to_string()), + active_attempt: hyperswitch_domain_models::RemoteStorageObject::ForeignID( + "nopes".to_string(), + ), business_country: None, business_label: None, order_details: None, @@ -3399,7 +3415,7 @@ impl AttemptType { // In case if fields are not overridden by the request then they contain the same data that was in the previous attempt provided it is populated in this function. #[inline(always)] fn make_new_payment_attempt( - payment_method_data: &Option, + payment_method_data: Option<&api_models::payments::PaymentMethodData>, old_payment_attempt: PaymentAttempt, new_attempt_count: i16, storage_scheme: enums::MerchantStorageScheme, @@ -3497,7 +3513,11 @@ impl AttemptType { let new_payment_attempt = db .insert_payment_attempt( Self::make_new_payment_attempt( - &request.payment_method_data, + request.payment_method_data.as_ref().and_then( + |request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }, + ), fetched_payment_attempt, new_attempt_count, storage_scheme, @@ -3514,7 +3534,11 @@ impl AttemptType { fetched_payment_intent, storage::PaymentIntentUpdate::StatusAndAttemptUpdate { status: payment_intent_status_fsm( - &request.payment_method_data, + request.payment_method_data.as_ref().and_then( + |request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }, + ), Some(true), ), active_attempt_id: new_payment_attempt.attempt_id.clone(), diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index d4590782b0a9..c214decb0b41 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -190,6 +190,16 @@ pub trait Domain: Send + Sync { ) -> CustomResult { Ok(false) } + + async fn store_extended_card_info_temporarily<'a>( + &'a self, + _state: &AppState, + _payment_id: &str, + _business_profile: &storage::BusinessProfile, + _payment_method_data: &Option, + ) -> CustomResult<(), errors::ApiErrorResponse> { + Ok(()) + } } #[async_trait] diff --git a/crates/router/src/core/payments/operations/payment_complete_authorize.rs b/crates/router/src/core/payments/operations/payment_complete_authorize.rs index d4dc52ce27cb..d31ec14a7231 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -138,7 +138,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -284,7 +284,7 @@ impl payment_method_data: request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), payment_method_info, force_sync: None, refunds: vec![], diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 66558faf2598..89c996236a39 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1,10 +1,11 @@ use std::marker::PhantomData; -use api_models::enums::FrmSuggestion; +use api_models::{admin::ExtendedCardInfoConfig, enums::FrmSuggestion, payments::ExtendedCardInfo}; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; use error_stack::{report, ResultExt}; use futures::FutureExt; +use masking::{ExposeInterface, PeekInterface}; use router_derive::PaymentOperation; use router_env::{instrument, logger, tracing}; use tracing_futures::Instrument; @@ -340,7 +341,7 @@ impl request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), )?; payment_attempt.browser_info = browser_info; @@ -411,7 +412,7 @@ impl let n_request_payment_method_data = request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()); + .and_then(|pmd| pmd.payment_method_data.clone()); let store = state.clone().store; let profile_id = payment_intent @@ -533,7 +534,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -579,11 +580,12 @@ impl let payment_method_data_after_card_bin_call = request .payment_method_data .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }) .zip(additional_pm_data) .map(|(payment_method_data, additional_payment_data)| { - payment_method_data - .payment_method_data - .apply_additional_payment_data(additional_payment_data) + payment_method_data.apply_additional_payment_data(additional_payment_data) }); let authentication = payment_attempt.authentication_id.as_ref().async_map(|authentication_id| async move { state @@ -905,6 +907,70 @@ impl Domain CustomResult { blocklist_utils::validate_data_for_blocklist(state, merchant_account, payment_data).await } + + #[instrument(skip_all)] + async fn store_extended_card_info_temporarily<'a>( + &'a self, + state: &AppState, + payment_id: &str, + business_profile: &storage::BusinessProfile, + payment_method_data: &Option, + ) -> CustomResult<(), errors::ApiErrorResponse> { + if let (Some(true), Some(api::PaymentMethodData::Card(card)), Some(merchant_config)) = ( + business_profile.is_extended_card_info_enabled, + payment_method_data, + business_profile.extended_card_info_config.clone(), + ) { + let merchant_config = merchant_config + .expose() + .parse_value::("ExtendedCardInfoConfig") + .map_err(|err| logger::error!(parse_err=?err,"Error while parsing ExtendedCardInfoConfig")); + + let card_data = ExtendedCardInfo::from(card.clone()) + .encode_to_vec() + .map_err(|err| logger::error!(encode_err=?err,"Error while encoding ExtendedCardInfo to vec")); + + let (Ok(merchant_config), Ok(card_data)) = (merchant_config, card_data) else { + return Ok(()); + }; + + let encrypted_payload = + services::encrypt_jwe(&card_data, merchant_config.public_key.peek()) + .await + .map_err(|err| { + logger::error!(jwe_encryption_err=?err,"Error while JWE encrypting extended card info") + }); + + let Ok(encrypted_payload) = encrypted_payload else { + return Ok(()); + }; + + let redis_conn = state + .store + .get_redis_conn() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get redis connection")?; + + let key = helpers::get_redis_key_for_extended_card_info( + &business_profile.merchant_id, + payment_id, + ); + + redis_conn + .set_key_with_expiry( + &key, + encrypted_payload.clone(), + (*merchant_config.ttl_in_secs).into(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add extended card info in redis")?; + + logger::info!("Extended card info added to redis"); + } + + Ok(()) + } } #[async_trait] diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index f43c258fefe0..3c7d2695a86b 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -5,12 +5,12 @@ use api_models::{ }; use async_trait::async_trait; use common_utils::ext_traits::{AsyncExt, Encode, ValueExt}; -use data_models::{ +use diesel_models::{ephemeral_key, PaymentMethod}; +use error_stack::{self, ResultExt}; +use hyperswitch_domain_models::{ mandates::{MandateData, MandateDetails}, payments::payment_attempt::PaymentAttempt, }; -use diesel_models::{ephemeral_key, PaymentMethod}; -use error_stack::{self, ResultExt}; use masking::{ExposeInterface, PeekInterface}; use router_derive::PaymentOperation; use router_env::{instrument, logger, tracing}; @@ -397,11 +397,14 @@ impl let payment_method_data_after_card_bin_call = request .payment_method_data .as_ref() + .and_then(|payment_method_data_from_request| { + payment_method_data_from_request + .payment_method_data + .as_ref() + }) .zip(additional_payment_data) .map(|(payment_method_data, additional_payment_data)| { - payment_method_data - .payment_method_data - .apply_additional_payment_data(additional_payment_data) + payment_method_data.apply_additional_payment_data(additional_payment_data) }); let amount = payment_attempt.get_total_amount().into(); @@ -703,7 +706,7 @@ impl ValidateRequest ValidateRequest, )> { + let payment_method_data = + request + .payment_method_data + .as_ref() + .and_then(|payment_method_data_request| { + payment_method_data_request.payment_method_data.as_ref() + }); + let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); - let status = - helpers::payment_attempt_status_fsm(&request.payment_method_data, request.confirm); + let status = helpers::payment_attempt_status_fsm(payment_method_data, request.confirm); let (amount, currency) = (money.0, Some(money.1)); let mut additional_pm_data = request .payment_method_data .as_ref() + .and_then(|payment_method_data_request| { + payment_method_data_request.payment_method_data.as_ref() + }) .async_map(|payment_method_data| async { - helpers::get_additional_payment_data( - &payment_method_data.payment_method_data, - &*state.store, - &profile_id, - ) - .await + helpers::get_additional_payment_data(payment_method_data, &*state.store,&profile_id,).await }) .await; @@ -947,8 +955,16 @@ impl PaymentCreate { session_expiry: PrimitiveDateTime, ) -> RouterResult { let created_at @ modified_at @ last_synced = Some(common_utils::date_time::now()); - let status = - helpers::payment_intent_status_fsm(&request.payment_method_data, request.confirm); + + let status = helpers::payment_intent_status_fsm( + request + .payment_method_data + .as_ref() + .and_then(|request_payment_method_data| { + request_payment_method_data.payment_method_data.as_ref() + }), + request.confirm, + ); let client_secret = crate::utils::generate_id(consts::ID_LENGTH, format!("{payment_id}_secret").as_str()); let (amount, currency) = (money.0, Some(money.1)); @@ -1002,7 +1018,9 @@ impl PaymentCreate { metadata: request.metadata.clone(), business_country: request.business_country, business_label: request.business_label.clone(), - active_attempt: data_models::RemoteStorageObject::ForeignID(active_attempt_id), + active_attempt: hyperswitch_domain_models::RemoteStorageObject::ForeignID( + active_attempt_id, + ), order_details, amount_captured: None, customer_id: None, diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 47d659b5ed20..f4408749fd3a 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -3,9 +3,9 @@ use std::collections::HashMap; use async_trait::async_trait; use common_enums::AuthorizationStatus; use common_utils::ext_traits::Encode; -use data_models::payments::payment_attempt::PaymentAttempt; use error_stack::{report, ResultExt}; use futures::FutureExt; +use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; use router_derive; use router_env::{instrument, logger, tracing}; use storage_impl::DataModelExt; diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 8ee474c684f1..0aaa60060521 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -81,7 +81,7 @@ impl request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), )?; helpers::validate_payment_status_against_not_allowed_statuses( @@ -265,7 +265,7 @@ impl &request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), &request.payment_method_type, &mandate_type, &token, @@ -430,7 +430,7 @@ impl payment_method_data: request .payment_method_data .as_ref() - .map(|pmd| pmd.payment_method_data.clone()), + .and_then(|pmd| pmd.payment_method_data.clone()), payment_method_info, force_sync: None, refunds: vec![], diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index e2502e2ecb6e..ff7303c900d0 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -171,10 +171,10 @@ where .customer_acceptance .clone() .map(|cat| match cat.acceptance_type { - data_models::mandates::AcceptanceType::Online => { + hyperswitch_domain_models::mandates::AcceptanceType::Online => { euclid_enums::MandateAcceptanceType::Online } - data_models::mandates::AcceptanceType::Offline => { + hyperswitch_domain_models::mandates::AcceptanceType::Offline => { euclid_enums::MandateAcceptanceType::Offline } }) @@ -184,10 +184,10 @@ where .as_ref() .and_then(|mandate_data| { mandate_data.mandate_type.clone().map(|mt| match mt { - data_models::mandates::MandateDataType::SingleUse(_) => { + hyperswitch_domain_models::mandates::MandateDataType::SingleUse(_) => { euclid_enums::MandateType::SingleUse } - data_models::mandates::MandateDataType::MultiUse(_) => { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(_) => { euclid_enums::MandateType::MultiUse } }) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index b891998dae35..d459f632d8f5 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -661,10 +661,10 @@ where customer_acceptance: d.customer_acceptance.map(|d| { api::CustomerAcceptance { acceptance_type: match d.acceptance_type { - data_models::mandates::AcceptanceType::Online => { + hyperswitch_domain_models::mandates::AcceptanceType::Online => { api::AcceptanceType::Online } - data_models::mandates::AcceptanceType::Offline => { + hyperswitch_domain_models::mandates::AcceptanceType::Offline => { api::AcceptanceType::Offline } }, @@ -676,7 +676,7 @@ where } }), mandate_type: d.mandate_type.map(|d| match d { - data_models::mandates::MandateDataType::MultiUse(Some(i)) => { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some(i)) => { api::MandateType::MultiUse(Some(api::MandateAmountData { amount: i.amount, currency: i.currency, @@ -685,7 +685,7 @@ where metadata: i.metadata, })) } - data_models::mandates::MandateDataType::SingleUse(i) => { + hyperswitch_domain_models::mandates::MandateDataType::SingleUse(i) => { api::MandateType::SingleUse(api::payments::MandateAmountData { amount: i.amount, currency: i.currency, @@ -694,7 +694,7 @@ where metadata: i.metadata, }) } - data_models::mandates::MandateDataType::MultiUse(None) => { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None) => { api::MandateType::MultiUse(None) } }), diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 738255580687..b297470bce84 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -7,9 +7,9 @@ use common_utils::{ ext_traits::{Encode, OptionExt}, types as common_types, }; -use data_models::payments::payment_attempt::PaymentAttempt; use diesel_models::business_profile::BusinessProfile; use error_stack::ResultExt; +use hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt; use redis_interface::errors::RedisError; use router_env::{instrument, tracing}; diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index bdaa0c01a100..25f619192070 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -7,12 +7,12 @@ use std::vec::IntoIter; use api_models::enums as api_enums; use common_utils::{consts, crypto::Encryptable, ext_traits::ValueExt, pii}; -#[cfg(feature = "olap")] -use data_models::errors::StorageError; use diesel_models::enums as storage_enums; use error_stack::{report, ResultExt}; #[cfg(feature = "olap")] use futures::future::join_all; +#[cfg(feature = "olap")] +use hyperswitch_domain_models::errors::StorageError; #[cfg(feature = "payout_retry")] use retry::GsmValidation; #[cfg(feature = "olap")] diff --git a/crates/router/src/core/payouts/validator.rs b/crates/router/src/core/payouts/validator.rs index 02ba3066544f..496aab13b13d 100644 --- a/crates/router/src/core/payouts/validator.rs +++ b/crates/router/src/core/payouts/validator.rs @@ -1,7 +1,7 @@ #[cfg(feature = "olap")] use common_utils::errors::CustomResult; -pub use data_models::errors::StorageError; use error_stack::{report, ResultExt}; +pub use hyperswitch_domain_models::errors::StorageError; use router_env::{instrument, tracing}; use super::helpers; diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index 6d5ae7a08c3a..09ea935ea76c 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -16,9 +16,9 @@ use common_utils::{ ext_traits::AsyncExt, generate_id, }; -use data_models::payments::PaymentIntent; use error_stack::ResultExt; use helpers::PaymentAuthConnectorDataExt; +use hyperswitch_domain_models::payments::PaymentIntent; use masking::{ExposeInterface, PeekInterface, Secret}; use pm_auth::{ connector::plaid::transformers::PlaidAuthType, diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 43d44ebe41d2..49248ae4fea5 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -107,7 +107,7 @@ pub fn validate_refund_list(limit: Option) -> CustomResult RouterResult<()> { let payment_method = payment_attempt diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 71d18774cb8f..0ae1b162e0ed 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -71,6 +71,26 @@ pub async fn signup_with_merchant_id( })) } +pub async fn get_user_details( + state: AppState, + user_from_token: auth::UserFromToken, +) -> UserResponse { + let user = user_from_token.get_user_from_db(&state).await?; + let verification_days_left = utils::user::get_verification_days_left(&state, &user)?; + + Ok(ApplicationResponse::Json( + user_api::GetUserDetailsResponse { + merchant_id: user_from_token.merchant_id, + name: user.get_name(), + email: user.get_email(), + user_id: user.get_user_id().to_string(), + verification_days_left, + role_id: user_from_token.role_id, + org_id: user_from_token.org_id, + }, + )) +} + pub async fn signup( state: AppState, request: user_api::SignUpRequest, @@ -103,7 +123,7 @@ pub async fn signup( pub async fn signin( state: AppState, request: user_api::SignInRequest, -) -> UserResponse { +) -> UserResponse { let user_from_db: domain::UserFromStorage = state .store .find_user_by_email(&request.email) @@ -141,6 +161,48 @@ pub async fn signin( let response = signin_strategy.get_signin_response(&state).await?; let token = utils::user::get_token_from_signin_response(&response); + auth::cookies::set_cookie_response( + user_api::SignInWithTokenResponse::SignInResponse(response), + token, + ) +} + +pub async fn signin_token_only_flow( + state: AppState, + request: user_api::SignInRequest, +) -> UserResponse { + let user_from_db: domain::UserFromStorage = state + .store + .find_user_by_email(&request.email) + .await + .to_not_found_response(UserErrors::InvalidCredentials)? + .into(); + + user_from_db.compare_password(request.password)?; + + let next_flow = + domain::NextFlow::from_origin(domain::Origin::SignIn, user_from_db.clone(), &state).await?; + + let token = match next_flow.get_flow() { + domain::UserFlow::SPTFlow(spt_flow) => spt_flow.generate_spt(&state, &next_flow).await, + domain::UserFlow::JWTFlow(jwt_flow) => { + #[cfg(feature = "email")] + { + user_from_db.get_verification_days_left(&state)?; + } + + let user_role = user_from_db + .get_preferred_or_active_user_role_from_db(&state) + .await + .to_not_found_response(UserErrors::InternalServerError)?; + jwt_flow.generate_jwt(&state, &next_flow, &user_role).await + } + }?; + + let response = user_api::SignInWithTokenResponse::Token(user_api::TokenResponse { + token: token.clone(), + token_type: next_flow.get_flow().into(), + }); auth::cookies::set_cookie_response(response, token) } @@ -1001,9 +1063,9 @@ pub async fn list_merchants_for_user( pub async fn get_user_details_in_merchant_account( state: AppState, user_from_token: auth::UserFromToken, - request: user_api::GetUserDetailsRequest, + request: user_api::GetUserRoleDetailsRequest, _req_state: ReqState, -) -> UserResponse { +) -> UserResponse { let required_user = utils::user::get_user_from_db_by_email(&state, request.email.try_into()?) .await .to_not_found_response(UserErrors::InvalidRoleOperation)?; @@ -1029,7 +1091,7 @@ pub async fn get_user_details_in_merchant_account( .attach_printable("User role exists but the corresponding role doesn't")?; Ok(ApplicationResponse::Json( - user_api::GetUserDetailsResponse { + user_api::GetUserRoleDetailsResponse { email: required_user.get_email(), name: required_user.get_name(), role_id: role_info.get_role_id().to_string(), diff --git a/crates/router/src/core/user/sample_data.rs b/crates/router/src/core/user/sample_data.rs index f95f48f19e79..e1bde652305f 100644 --- a/crates/router/src/core/user/sample_data.rs +++ b/crates/router/src/core/user/sample_data.rs @@ -1,7 +1,7 @@ use api_models::user::sample_data::SampleDataRequest; use common_utils::errors::ReportSwitchExt; -use data_models::payments::payment_intent::PaymentIntentNew; use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; +use hyperswitch_domain_models::payments::payment_intent::PaymentIntentNew; pub type SampleDataApiResponse = SampleDataResult>; diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index b8399251530a..3f913b88a3f8 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -170,7 +170,7 @@ pub async fn transfer_org_ownership( pub async fn accept_invitation( state: AppState, - user_token: auth::UserWithoutMerchantFromToken, + user_token: auth::UserFromSinglePurposeToken, req: user_role_api::AcceptInvitationRequest, _req_state: ReqState, ) -> UserResponse { diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index c7a22e568970..3923663f5d12 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -976,7 +976,7 @@ pub fn is_merchant_enabled_for_payment_id_as_connector_request_id( pub fn get_connector_request_reference_id( conf: &Settings, merchant_id: &str, - payment_attempt: &data_models::payments::payment_attempt::PaymentAttempt, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, ) -> String { let is_config_enabled_for_merchant = is_merchant_enabled_for_payment_id_as_connector_request_id(conf, merchant_id); diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index eb6faea5c3bc..6647549e399e 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -307,8 +307,10 @@ pub async fn get_payment_attempt_from_object_reference_id( state: &AppState, object_reference_id: api_models::webhooks::ObjectReferenceId, merchant_account: &domain::MerchantAccount, -) -> CustomResult -{ +) -> CustomResult< + hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + errors::ApiErrorResponse, +> { let db = &*state.store; match object_reference_id { api::ObjectReferenceId::PaymentId(api::PaymentIdType::ConnectorTransactionId(ref id)) => db @@ -346,7 +348,7 @@ pub async fn get_or_update_dispute_object( option_dispute: Option, dispute_details: api::disputes::DisputePayload, merchant_id: &str, - payment_attempt: &data_models::payments::payment_attempt::PaymentAttempt, + payment_attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, event_type: api_models::webhooks::IncomingWebhookEvent, business_profile: &diesel_models::business_profile::BusinessProfile, connector_name: &str, diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 9f9e61a5ffcc..9b222486d18a 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -35,18 +35,20 @@ pub mod routing_algorithm; pub mod user; pub mod user_role; -use data_models::payments::{ - payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, -}; -#[cfg(feature = "payouts")] -use data_models::payouts::{payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface}; -#[cfg(not(feature = "payouts"))] -use data_models::{PayoutAttemptInterface, PayoutsInterface}; use diesel_models::{ fraud_check::{FraudCheck, FraudCheckNew, FraudCheckUpdate}, organization::{Organization, OrganizationNew, OrganizationUpdate}, }; use error_stack::ResultExt; +use hyperswitch_domain_models::payments::{ + payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, +}; +#[cfg(feature = "payouts")] +use hyperswitch_domain_models::payouts::{ + payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface, +}; +#[cfg(not(feature = "payouts"))] +use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; use masking::PeekInterface; use redis_interface::errors::RedisError; use storage_impl::{errors::StorageError, redis::kv_store::RedisConnInterface, MockDb}; diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 34bf7552a487..0aaa47365fc2 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -2,13 +2,6 @@ use std::sync::Arc; use common_enums::enums::MerchantStorageScheme; use common_utils::{errors::CustomResult, pii}; -use data_models::payments::{ - payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, -}; -#[cfg(feature = "payouts")] -use data_models::payouts::{payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface}; -#[cfg(not(feature = "payouts"))] -use data_models::{PayoutAttemptInterface, PayoutsInterface}; use diesel_models::{ enums, enums::ProcessTrackerStatus, @@ -16,6 +9,15 @@ use diesel_models::{ reverse_lookup::{ReverseLookup, ReverseLookupNew}, user_role as user_storage, }; +use hyperswitch_domain_models::payments::{ + payment_attempt::PaymentAttemptInterface, payment_intent::PaymentIntentInterface, +}; +#[cfg(feature = "payouts")] +use hyperswitch_domain_models::payouts::{ + payout_attempt::PayoutAttemptInterface, payouts::PayoutsInterface, +}; +#[cfg(not(feature = "payouts"))] +use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; use masking::Secret; use redis_interface::{errors::RedisError, RedisConnectionPool, RedisEntryId}; use router_env::logger; @@ -1238,11 +1240,11 @@ impl PaymentAttemptInterface for KafkaStore { async fn get_filters_for_payments( &self, - pi: &[data_models::payments::PaymentIntent], + pi: &[hyperswitch_domain_models::payments::PaymentIntent], merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> CustomResult< - data_models::payments::payment_attempt::PaymentListFilters, + hyperswitch_domain_models::payments::payment_attempt::PaymentListFilters, errors::DataStorageError, > { self.diesel_store @@ -1344,7 +1346,7 @@ impl PaymentIntentInterface for KafkaStore { async fn filter_payment_intent_by_constraints( &self, merchant_id: &str, - filters: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + filters: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { self.diesel_store @@ -1372,12 +1374,12 @@ impl PaymentIntentInterface for KafkaStore { async fn get_filtered_payment_intents_attempt( &self, merchant_id: &str, - constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult< Vec<( - data_models::payments::PaymentIntent, - data_models::payments::payment_attempt::PaymentAttempt, + hyperswitch_domain_models::payments::PaymentIntent, + hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, )>, errors::DataStorageError, > { @@ -1390,7 +1392,7 @@ impl PaymentIntentInterface for KafkaStore { async fn get_filtered_active_attempt_ids_for_total_count( &self, merchant_id: &str, - constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { self.diesel_store @@ -1584,11 +1586,11 @@ impl PayoutAttemptInterface for KafkaStore { async fn get_filters_for_payouts( &self, - payouts: &[data_models::payouts::payouts::Payouts], + payouts: &[hyperswitch_domain_models::payouts::payouts::Payouts], merchant_id: &str, storage_scheme: MerchantStorageScheme, ) -> CustomResult< - data_models::payouts::payout_attempt::PayoutListFilters, + hyperswitch_domain_models::payouts::payout_attempt::PayoutListFilters, errors::DataStorageError, > { self.diesel_store @@ -1663,7 +1665,7 @@ impl PayoutsInterface for KafkaStore { async fn filter_payouts_by_constraints( &self, merchant_id: &str, - filters: &data_models::payouts::PayoutFetchConstraints, + filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult, errors::DataStorageError> { self.diesel_store @@ -1675,7 +1677,7 @@ impl PayoutsInterface for KafkaStore { async fn filter_payouts_and_attempts( &self, merchant_id: &str, - filters: &data_models::payouts::PayoutFetchConstraints, + filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints, storage_scheme: MerchantStorageScheme, ) -> CustomResult< Vec<( @@ -2482,9 +2484,11 @@ impl DashboardMetadataInterface for KafkaStore { impl BatchSampleDataInterface for KafkaStore { async fn insert_payment_intents_batch_for_sample_data( &self, - batch: Vec, - ) -> CustomResult, data_models::errors::StorageError> - { + batch: Vec, + ) -> CustomResult< + Vec, + hyperswitch_domain_models::errors::StorageError, + > { let payment_intents_list = self .diesel_store .insert_payment_intents_batch_for_sample_data(batch) @@ -2503,8 +2507,8 @@ impl BatchSampleDataInterface for KafkaStore { &self, batch: Vec, ) -> CustomResult< - Vec, - data_models::errors::StorageError, + Vec, + hyperswitch_domain_models::errors::StorageError, > { let payment_attempts_list = self .diesel_store @@ -2523,7 +2527,8 @@ impl BatchSampleDataInterface for KafkaStore { async fn insert_refunds_batch_for_sample_data( &self, batch: Vec, - ) -> CustomResult, data_models::errors::StorageError> { + ) -> CustomResult, hyperswitch_domain_models::errors::StorageError> + { let refunds_list = self .diesel_store .insert_refunds_batch_for_sample_data(batch) @@ -2538,8 +2543,10 @@ impl BatchSampleDataInterface for KafkaStore { async fn delete_payment_intents_for_sample_data( &self, merchant_id: &str, - ) -> CustomResult, data_models::errors::StorageError> - { + ) -> CustomResult< + Vec, + hyperswitch_domain_models::errors::StorageError, + > { let payment_intents_list = self .diesel_store .delete_payment_intents_for_sample_data(merchant_id) @@ -2558,8 +2565,8 @@ impl BatchSampleDataInterface for KafkaStore { &self, merchant_id: &str, ) -> CustomResult< - Vec, - data_models::errors::StorageError, + Vec, + hyperswitch_domain_models::errors::StorageError, > { let payment_attempts_list = self .diesel_store @@ -2579,7 +2586,8 @@ impl BatchSampleDataInterface for KafkaStore { async fn delete_refunds_for_sample_data( &self, merchant_id: &str, - ) -> CustomResult, data_models::errors::StorageError> { + ) -> CustomResult, hyperswitch_domain_models::errors::StorageError> + { let refunds_list = self .diesel_store .delete_refunds_for_sample_data(merchant_id) diff --git a/crates/router/src/db/user/sample_data.rs b/crates/router/src/db/user/sample_data.rs index ae98332cfc49..f7926021afd0 100644 --- a/crates/router/src/db/user/sample_data.rs +++ b/crates/router/src/db/user/sample_data.rs @@ -1,7 +1,3 @@ -use data_models::{ - errors::StorageError, - payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntentNew, PaymentIntent}, -}; use diesel_models::{ errors::DatabaseError, query::user::sample_data as sample_data_queries, @@ -9,6 +5,10 @@ use diesel_models::{ user::sample_data::PaymentAttemptBatchNew, }; use error_stack::{Report, ResultExt}; +use hyperswitch_domain_models::{ + errors::StorageError, + payments::{payment_attempt::PaymentAttempt, payment_intent::PaymentIntentNew, PaymentIntent}, +}; use storage_impl::DataModelExt; use crate::{connection::pg_connection_write, core::errors::CustomResult, services::Store}; diff --git a/crates/router/src/events.rs b/crates/router/src/events.rs index d226c2bdbeda..4bca58248351 100644 --- a/crates/router/src/events.rs +++ b/crates/router/src/events.rs @@ -1,6 +1,6 @@ -use data_models::errors::{StorageError, StorageResult}; use error_stack::ResultExt; use events::{EventsError, Message, MessagingInterface}; +use hyperswitch_domain_models::errors::{StorageError, StorageResult}; use masking::ErasedMaskSerialize; use router_env::logger; use serde::{Deserialize, Serialize}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index d42d3fe0039d..19a632bf895e 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -41,6 +41,8 @@ use super::{currency, payment_methods::*}; use super::{ephemeral_key::*, webhooks::*}; #[cfg(feature = "oltp")] use super::{pm_auth, poll::retrieve_poll_status}; +#[cfg(feature = "olap")] +pub use crate::analytics::opensearch::OpenSearchClient; use crate::configs::secrets_transformers; #[cfg(all(feature = "frm", feature = "oltp"))] use crate::routes::fraud_check as frm_routes; @@ -73,6 +75,8 @@ pub struct AppState { pub api_client: Box, #[cfg(feature = "olap")] pub pool: crate::analytics::AnalyticsProvider, + #[cfg(feature = "olap")] + pub opensearch_client: OpenSearchClient, pub request_id: Option, pub file_storage_client: Box, pub encryption_client: Box, @@ -177,6 +181,14 @@ impl AppState { .await .expect("Failed to create event handler"); + #[allow(clippy::expect_used)] + #[cfg(feature = "olap")] + let opensearch_client = conf + .opensearch + .get_opensearch_client() + .await + .expect("Failed to create opensearch client"); + let store: Box = match storage_impl { StorageImpl::Postgresql | StorageImpl::PostgresqlTest => match &event_handler { EventsHandler::Kafka(kafka_client) => Box::new( @@ -223,6 +235,8 @@ impl AppState { event_handler, #[cfg(feature = "olap")] pool, + #[cfg(feature = "olap")] + opensearch_client, request_id: None, file_storage_client, encryption_client, @@ -1163,6 +1177,7 @@ impl User { let mut route = web::scope("/user").app_data(web::Data::new(state)); route = route + .service(web::resource("").route(web::get().to(get_user_details))) .service(web::resource("/v2/signin").route(web::post().to(user_signin))) .service(web::resource("/signout").route(web::post().to(signout))) .service(web::resource("/change_password").route(web::post().to(change_password))) diff --git a/crates/router/src/routes/health.rs b/crates/router/src/routes/health.rs index fbfcd893a636..7d35a91a31eb 100644 --- a/crates/router/src/routes/health.rs +++ b/crates/router/src/routes/health.rs @@ -74,6 +74,10 @@ async fn deep_health_check_func(state: app::AppState) -> RouterResponse RouterResponse RouterResponse RouterResponse for ApiIdentifier { | Flow::DeleteSampleData | Flow::UserMerchantAccountList | Flow::GetUserDetails + | Flow::GetUserRoleDetails | Flow::ListUsersForMerchantAccount | Flow::ForgotPassword | Flow::ResetPassword diff --git a/crates/router/src/routes/user.rs b/crates/router/src/routes/user.rs index bf3ddbc4491d..4d841913bc0c 100644 --- a/crates/router/src/routes/user.rs +++ b/crates/router/src/routes/user.rs @@ -19,6 +19,20 @@ use crate::{ utils::user::dashboard_metadata::{parse_string_to_enums, set_ip_address_if_required}, }; +pub async fn get_user_details(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::GetUserDetails; + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, user, _, _| user_core::get_user_details(state, user), + &auth::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "email")] pub async fn user_signup_with_merchant_id( state: web::Data, @@ -62,15 +76,23 @@ pub async fn user_signin( state: web::Data, http_req: HttpRequest, json_payload: web::Json, + query: web::Query, ) -> HttpResponse { let flow = Flow::UserSignIn; let req_payload = json_payload.into_inner(); + let is_token_only = query.into_inner().token_only; Box::pin(api::server_wrap( flow.clone(), state, &http_req, req_payload.clone(), - |state, _, req_body, _| user_core::signin(state, req_body), + |state, _, req_body, _| async move { + if let Some(true) = is_token_only { + user_core::signin_token_only_flow(state, req_body).await + } else { + user_core::signin(state, req_body).await + } + }, &auth::NoAuth, api_locking::LockAction::NotApplicable, )) @@ -295,7 +317,7 @@ pub async fn list_merchants_for_user(state: web::Data, req: HttpReques pub async fn get_user_role_details( state: web::Data, req: HttpRequest, - payload: web::Query, + payload: web::Query, ) -> HttpResponse { let flow = Flow::GetUserDetails; Box::pin(api::server_wrap( diff --git a/crates/router/src/routes/user_role.rs b/crates/router/src/routes/user_role.rs index 7ffcc8d09df0..59c07b798555 100644 --- a/crates/router/src/routes/user_role.rs +++ b/crates/router/src/routes/user_role.rs @@ -1,5 +1,6 @@ use actix_web::{web, HttpRequest, HttpResponse}; use api_models::user_role::{self as user_role_api, role as role_api}; +use common_enums::TokenPurpose; use router_env::Flow; use super::AppState; @@ -214,7 +215,7 @@ pub async fn accept_invitation( &req, payload, user_role_core::accept_invitation, - &auth::UserWithoutMerchantJWTAuth, + &auth::SinglePurposeJWTAuth(TokenPurpose::AcceptInvite), api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/services.rs b/crates/router/src/services.rs index 6b604498c713..5af325a46f76 100644 --- a/crates/router/src/services.rs +++ b/crates/router/src/services.rs @@ -13,8 +13,8 @@ pub mod recon; #[cfg(feature = "email")] pub mod email; -use data_models::errors::StorageResult; use error_stack::ResultExt; +use hyperswitch_domain_models::errors::StorageResult; use masking::{ExposeInterface, StrongSecret}; #[cfg(feature = "kv_store")] use storage_impl::KVRouterStore; diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 8652f51451af..5f15474115dd 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -4,6 +4,7 @@ use api_models::{ payments, }; use async_trait::async_trait; +use common_enums::TokenPurpose; use common_utils::date_time; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; @@ -66,7 +67,7 @@ pub enum AuthenticationType { }, SinglePurposeJWT { user_id: String, - purpose: Purpose, + purpose: TokenPurpose, }, MerchantId { merchant_id: String, @@ -113,28 +114,28 @@ impl AuthenticationType { } } +#[cfg(feature = "olap")] #[derive(Clone, Debug)] pub struct UserFromSinglePurposeToken { pub user_id: String, + pub origin: domain::Origin, } +#[cfg(feature = "olap")] #[derive(serde::Serialize, serde::Deserialize)] pub struct SinglePurposeToken { pub user_id: String, - pub purpose: Purpose, + pub purpose: TokenPurpose, + pub origin: domain::Origin, pub exp: u64, } -#[derive(Debug, Clone, PartialEq, Eq, strum::Display, serde::Deserialize, serde::Serialize)] -pub enum Purpose { - AcceptInvite, -} - #[cfg(feature = "olap")] impl SinglePurposeToken { pub async fn new_token( user_id: String, - purpose: Purpose, + purpose: TokenPurpose, + origin: domain::Origin, settings: &Settings, ) -> UserResult { let exp_duration = @@ -143,34 +144,13 @@ impl SinglePurposeToken { let token_payload = Self { user_id, purpose, + origin, exp, }; jwt::generate_jwt(&token_payload, settings).await } } -// TODO: This has to be removed once single purpose token is used as a intermediate token -#[derive(Clone, Debug)] -pub struct UserWithoutMerchantFromToken { - pub user_id: String, -} - -#[derive(serde::Serialize, serde::Deserialize)] -pub struct UserAuthToken { - pub user_id: String, - pub exp: u64, -} - -#[cfg(feature = "olap")] -impl UserAuthToken { - pub async fn new_token(user_id: String, settings: &Settings) -> UserResult { - let exp_duration = std::time::Duration::from_secs(consts::JWT_TOKEN_TIME_IN_SECS); - let exp = jwt::generate_exp(exp_duration)?.as_secs(); - let token_payload = Self { user_id, exp }; - jwt::generate_jwt(&token_payload, settings).await - } -} - #[derive(serde::Serialize, serde::Deserialize)] pub struct AuthToken { pub user_id: String, @@ -330,39 +310,9 @@ where } } -#[derive(Debug)] -pub struct UserWithoutMerchantJWTAuth; - #[cfg(feature = "olap")] -#[async_trait] -impl AuthenticateAndFetch for UserWithoutMerchantJWTAuth -where - A: AppStateInfo + Sync, -{ - async fn authenticate_and_fetch( - &self, - request_headers: &HeaderMap, - state: &A, - ) -> RouterResult<(UserWithoutMerchantFromToken, AuthenticationType)> { - let payload = parse_jwt_payload::(request_headers, state).await?; - if payload.check_in_blacklist(state).await? { - return Err(errors::ApiErrorResponse::InvalidJwtToken.into()); - } - - Ok(( - UserWithoutMerchantFromToken { - user_id: payload.user_id.clone(), - }, - AuthenticationType::UserJwt { - user_id: payload.user_id, - }, - )) - } -} - -#[allow(dead_code)] #[derive(Debug)] -pub(crate) struct SinglePurposeJWTAuth(pub Purpose); +pub(crate) struct SinglePurposeJWTAuth(pub TokenPurpose); #[cfg(feature = "olap")] #[async_trait] @@ -387,6 +337,7 @@ where Ok(( UserFromSinglePurposeToken { user_id: payload.user_id.clone(), + origin: payload.origin.clone(), }, AuthenticationType::SinglePurposeJWT { user_id: payload.user_id, diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs index b2f63f4927e2..c1abb0647525 100644 --- a/crates/router/src/services/authentication/blacklist.rs +++ b/crates/router/src/services/authentication/blacklist.rs @@ -5,7 +5,9 @@ use common_utils::date_time; use error_stack::ResultExt; use redis_interface::RedisConnectionPool; -use super::{AuthToken, SinglePurposeToken, UserAuthToken}; +use super::AuthToken; +#[cfg(feature = "olap")] +use super::SinglePurposeToken; #[cfg(feature = "email")] use crate::consts::{EMAIL_TOKEN_BLACKLIST_PREFIX, EMAIL_TOKEN_TIME_IN_SECS}; use crate::{ @@ -154,16 +156,7 @@ impl BlackList for AuthToken { } } -#[async_trait::async_trait] -impl BlackList for UserAuthToken { - async fn check_in_blacklist(&self, state: &A) -> RouterResult - where - A: AppStateInfo + Sync, - { - check_user_in_blacklist(state, &self.user_id, self.exp).await - } -} - +#[cfg(feature = "olap")] #[async_trait::async_trait] impl BlackList for SinglePurposeToken { async fn check_in_blacklist(&self, state: &A) -> RouterResult diff --git a/crates/router/src/services/kafka.rs b/crates/router/src/services/kafka.rs index 3ae923e246ff..ba9806b29d19 100644 --- a/crates/router/src/services/kafka.rs +++ b/crates/router/src/services/kafka.rs @@ -15,8 +15,8 @@ mod dispute; mod payment_attempt; mod payment_intent; mod refund; -use data_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use diesel_models::refund::Refund; +use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use serde::Serialize; use time::{OffsetDateTime, PrimitiveDateTime}; diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index 787a633b0c5b..9f442876fab1 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -1,6 +1,8 @@ // use diesel_models::enums::MandateDetails; -use data_models::{mandates::MandateDetails, payments::payment_attempt::PaymentAttempt}; use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::{ + mandates::MandateDetails, payments::payment_attempt::PaymentAttempt, +}; use time::OffsetDateTime; #[derive(serde::Serialize, Debug)] diff --git a/crates/router/src/services/kafka/payment_intent.rs b/crates/router/src/services/kafka/payment_intent.rs index 6a756c664d41..2edd0d49cccf 100644 --- a/crates/router/src/services/kafka/payment_intent.rs +++ b/crates/router/src/services/kafka/payment_intent.rs @@ -1,5 +1,5 @@ -use data_models::payments::PaymentIntent; use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::payments::PaymentIntent; use time::OffsetDateTime; #[derive(serde::Serialize, Debug)] diff --git a/crates/router/src/services/kafka/payout.rs b/crates/router/src/services/kafka/payout.rs index e2bd8ef83fdf..6b25c724eb8c 100644 --- a/crates/router/src/services/kafka/payout.rs +++ b/crates/router/src/services/kafka/payout.rs @@ -1,6 +1,6 @@ use common_utils::pii; -use data_models::payouts::{payout_attempt::PayoutAttempt, payouts::Payouts}; use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::payouts::{payout_attempt::PayoutAttempt, payouts::Payouts}; use time::OffsetDateTime; #[derive(serde::Serialize, Debug)] diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 77d53584dabd..bffa3eaf8f8a 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -24,8 +24,8 @@ pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; use common_enums::MandateStatus; pub use common_utils::request::RequestContent; use common_utils::{pii, pii::Email}; -use data_models::mandates::{CustomerAcceptance, MandateData}; use error_stack::ResultExt; +use hyperswitch_domain_models::mandates::{CustomerAcceptance, MandateData}; use masking::Secret; use serde::Serialize; diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 38fd773b9b32..57f51c7b8b05 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -93,7 +93,7 @@ pub trait ConnectorMandateRevoke: pub trait ConnectorTransactionId: ConnectorCommon + Sync { fn connector_transaction_id( &self, - payment_attempt: data_models::payments::payment_attempt::PaymentAttempt, + payment_attempt: hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, ) -> Result, errors::ApiErrorResponse> { Ok(payment_attempt.connector_transaction_id) } diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 4fc3fb445218..e36c68c27d56 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -261,7 +261,7 @@ mod payments_test { PaymentsRequest { amount: Some(Amount::from(200)), payment_method_data: Some(PaymentMethodDataRequest { - payment_method_data: PaymentMethodData::Card(card()), + payment_method_data: Some(PaymentMethodData::Card(card())), billing: None, }), ..PaymentsRequest::default() diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 46f8e1340e53..aed1bb02e727 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -3,6 +3,7 @@ use std::{collections::HashSet, ops, str::FromStr}; use api_models::{ admin as admin_api, organization as api_org, user as user_api, user_role as user_role_api, }; +use common_enums::TokenPurpose; use common_utils::{errors::CustomResult, pii}; use diesel_models::{ enums::UserStatus, @@ -31,6 +32,8 @@ use crate::{ }; pub mod dashboard_metadata; +pub mod decision_manager; +pub use decision_manager::*; #[derive(Clone)] pub struct UserName(Secret); @@ -155,7 +158,32 @@ pub struct UserPassword(Secret); impl UserPassword { pub fn new(password: Secret) -> UserResult { let password = password.expose(); - if password.is_empty() { + + let mut has_upper_case = false; + let mut has_lower_case = false; + let mut has_numeric_value = false; + let mut has_special_character = false; + let mut has_whitespace = false; + + for c in password.chars() { + has_upper_case = has_upper_case || c.is_uppercase(); + has_lower_case = has_lower_case || c.is_lowercase(); + has_numeric_value = has_numeric_value || c.is_numeric(); + has_special_character = + has_special_character || !(c.is_alphanumeric() && c.is_whitespace()); + has_whitespace = has_whitespace || c.is_whitespace(); + } + + let is_password_format_valid = has_upper_case + && has_lower_case + && has_numeric_value + && has_special_character + && !has_whitespace; + + let is_too_long = password.graphemes(true).count() > consts::user::MAX_PASSWORD_LENGTH; + let is_too_short = password.graphemes(true).count() < consts::user::MIN_PASSWORD_LENGTH; + + if is_too_short || is_too_long || !is_password_format_valid { Err(UserErrors::PasswordParsingError.into()) } else { Ok(Self(password.into())) @@ -785,6 +813,29 @@ impl UserFromStorage { .find_user_role_by_user_id_merchant_id(self.get_user_id(), merchant_id) .await } + + pub async fn get_preferred_or_active_user_role_from_db( + &self, + state: &AppState, + ) -> CustomResult { + if let Some(preferred_merchant_id) = self.get_preferred_merchant_id() { + self.get_role_from_db_by_merchant_id(state, &preferred_merchant_id) + .await + } else { + state + .store + .list_user_roles_by_user_id(&self.0.user_id) + .await? + .into_iter() + .find(|role| role.status == UserStatus::Active) + .ok_or( + errors::StorageError::ValueNotFound( + "No active role found for user".to_string(), + ) + .into(), + ) + } + } } impl From for user_role_api::ModuleInfo { @@ -910,8 +961,10 @@ impl SignInWithMultipleRolesStrategy { user_api::MerchantSelectResponse { name: self.user.get_name(), email: self.user.get_email(), - token: auth::UserAuthToken::new_token( + token: auth::SinglePurposeToken::new_token( self.user.get_user_id().to_string(), + TokenPurpose::AcceptInvite, + Origin::SignIn, &state.conf, ) .await? diff --git a/crates/router/src/types/domain/user/decision_manager.rs b/crates/router/src/types/domain/user/decision_manager.rs new file mode 100644 index 000000000000..b5aff7791066 --- /dev/null +++ b/crates/router/src/types/domain/user/decision_manager.rs @@ -0,0 +1,257 @@ +use common_enums::TokenPurpose; +use diesel_models::{enums::UserStatus, user_role::UserRole}; +use masking::Secret; + +use super::UserFromStorage; +use crate::{ + core::errors::{UserErrors, UserResult}, + routes::AppState, + services::authentication as auth, +}; + +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum UserFlow { + SPTFlow(SPTFlow), + JWTFlow(JWTFlow), +} + +impl UserFlow { + async fn is_required(&self, user: &UserFromStorage, state: &AppState) -> UserResult { + match self { + Self::SPTFlow(flow) => flow.is_required(user, state).await, + Self::JWTFlow(flow) => flow.is_required(user, state).await, + } + } +} + +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum SPTFlow { + TOTP, + VerifyEmail, + AcceptInvitationFromEmail, + ForceSetPassword, + MerchantSelect, + ResetPassword, +} + +impl SPTFlow { + async fn is_required(&self, user: &UserFromStorage, state: &AppState) -> UserResult { + match self { + // TOTP + Self::TOTP => Ok(true), + // Main email APIs + Self::AcceptInvitationFromEmail | Self::ResetPassword => Ok(true), + Self::VerifyEmail => Ok(user.0.is_verified), + // Final Checks + // TODO: this should be based on last_password_modified_at as a placeholder using false + Self::ForceSetPassword => Ok(false), + Self::MerchantSelect => user + .get_roles_from_db(state) + .await + .map(|roles| !roles.iter().any(|role| role.status == UserStatus::Active)), + } + } + + pub async fn generate_spt( + self, + state: &AppState, + next_flow: &NextFlow, + ) -> UserResult> { + auth::SinglePurposeToken::new_token( + next_flow.user.get_user_id().to_string(), + self.into(), + next_flow.origin.clone(), + &state.conf, + ) + .await + .map(|token| token.into()) + } +} + +#[derive(Eq, PartialEq, Clone, Copy)] +pub enum JWTFlow { + UserInfo, +} + +impl JWTFlow { + async fn is_required(&self, _user: &UserFromStorage, _state: &AppState) -> UserResult { + Ok(true) + } + + pub async fn generate_jwt( + self, + state: &AppState, + next_flow: &NextFlow, + user_role: &UserRole, + ) -> UserResult> { + auth::AuthToken::new_token( + next_flow.user.get_user_id().to_string(), + user_role.merchant_id.clone(), + user_role.role_id.clone(), + &state.conf, + user_role.org_id.clone(), + ) + .await + .map(|token| token.into()) + } +} + +#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)] +#[serde(rename_all = "snake_case")] +pub enum Origin { + SignIn, + SignUp, + MagicLink, + VerifyEmail, + AcceptInvitationFromEmail, + ResetPassword, +} + +impl Origin { + fn get_flows(&self) -> &'static [UserFlow] { + match self { + Self::SignIn => &SIGNIN_FLOW, + Self::SignUp => &SIGNUP_FLOW, + Self::VerifyEmail => &VERIFY_EMAIL_FLOW, + Self::MagicLink => &MAGIC_LINK_FLOW, + Self::AcceptInvitationFromEmail => &ACCEPT_INVITATION_FROM_EMAIL_FLOW, + Self::ResetPassword => &RESET_PASSWORD_FLOW, + } + } +} + +const SIGNIN_FLOW: [UserFlow; 4] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::ForceSetPassword), + UserFlow::SPTFlow(SPTFlow::MerchantSelect), + UserFlow::JWTFlow(JWTFlow::UserInfo), +]; + +const SIGNUP_FLOW: [UserFlow; 4] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::ForceSetPassword), + UserFlow::SPTFlow(SPTFlow::MerchantSelect), + UserFlow::JWTFlow(JWTFlow::UserInfo), +]; + +const MAGIC_LINK_FLOW: [UserFlow; 5] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::VerifyEmail), + UserFlow::SPTFlow(SPTFlow::ForceSetPassword), + UserFlow::SPTFlow(SPTFlow::MerchantSelect), + UserFlow::JWTFlow(JWTFlow::UserInfo), +]; + +const VERIFY_EMAIL_FLOW: [UserFlow; 5] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::VerifyEmail), + UserFlow::SPTFlow(SPTFlow::ForceSetPassword), + UserFlow::SPTFlow(SPTFlow::MerchantSelect), + UserFlow::JWTFlow(JWTFlow::UserInfo), +]; + +const ACCEPT_INVITATION_FROM_EMAIL_FLOW: [UserFlow; 4] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::AcceptInvitationFromEmail), + UserFlow::SPTFlow(SPTFlow::ForceSetPassword), + UserFlow::JWTFlow(JWTFlow::UserInfo), +]; + +const RESET_PASSWORD_FLOW: [UserFlow; 2] = [ + UserFlow::SPTFlow(SPTFlow::TOTP), + UserFlow::SPTFlow(SPTFlow::ResetPassword), +]; + +pub struct CurrentFlow { + origin: Origin, + current_flow_index: usize, +} + +impl CurrentFlow { + pub fn new(origin: Origin, current_flow: UserFlow) -> UserResult { + let flows = origin.get_flows(); + let index = flows + .iter() + .position(|flow| flow == ¤t_flow) + .ok_or(UserErrors::InternalServerError)?; + + Ok(Self { + origin, + current_flow_index: index, + }) + } + + pub async fn next(&self, user: UserFromStorage, state: &AppState) -> UserResult { + let flows = self.origin.get_flows(); + let remaining_flows = flows.iter().skip(self.current_flow_index + 1); + for flow in remaining_flows { + if flow.is_required(&user, state).await? { + return Ok(NextFlow { + origin: self.origin.clone(), + next_flow: *flow, + user, + }); + } + } + Err(UserErrors::InternalServerError.into()) + } +} + +pub struct NextFlow { + origin: Origin, + next_flow: UserFlow, + user: UserFromStorage, +} + +impl NextFlow { + pub async fn from_origin( + origin: Origin, + user: UserFromStorage, + state: &AppState, + ) -> UserResult { + let flows = origin.get_flows(); + for flow in flows { + if flow.is_required(&user, state).await? { + return Ok(Self { + origin, + next_flow: *flow, + user, + }); + } + } + Err(UserErrors::InternalServerError.into()) + } + + pub fn get_flow(&self) -> UserFlow { + self.next_flow + } +} + +impl From for TokenPurpose { + fn from(value: UserFlow) -> Self { + match value { + UserFlow::SPTFlow(flow) => flow.into(), + UserFlow::JWTFlow(flow) => flow.into(), + } + } +} + +impl From for TokenPurpose { + fn from(value: SPTFlow) -> Self { + match value { + SPTFlow::TOTP => Self::TOTP, + SPTFlow::VerifyEmail => Self::VerifyEmail, + SPTFlow::AcceptInvitationFromEmail => Self::AcceptInvitationFromEmail, + SPTFlow::MerchantSelect => Self::AcceptInvite, + SPTFlow::ResetPassword | SPTFlow::ForceSetPassword => Self::ResetPassword, + } + } +} + +impl From for TokenPurpose { + fn from(value: JWTFlow) -> Self { + match value { + JWTFlow::UserInfo => Self::UserInfo, + } + } +} diff --git a/crates/router/src/types/storage.rs b/crates/router/src/types/storage.rs index ec164000b32c..77550bedabe0 100644 --- a/crates/router/src/types/storage.rs +++ b/crates/router/src/types/storage.rs @@ -39,19 +39,19 @@ pub mod user_role; use std::collections::HashMap; -pub use data_models::payments::{ +pub use diesel_models::{ + ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner, ProcessTrackerUpdate, +}; +pub use hyperswitch_domain_models::payments::{ payment_attempt::{PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate}, payment_intent::{PaymentIntentNew, PaymentIntentUpdate}, PaymentIntent, }; #[cfg(feature = "payouts")] -pub use data_models::payouts::{ +pub use hyperswitch_domain_models::payouts::{ payout_attempt::{PayoutAttempt, PayoutAttemptNew, PayoutAttemptUpdate}, payouts::{Payouts, PayoutsNew, PayoutsUpdate}, }; -pub use diesel_models::{ - ProcessTracker, ProcessTrackerNew, ProcessTrackerRunner, ProcessTrackerUpdate, -}; pub use scheduler::db::process_tracker; pub use self::{ diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 0d840326f9b3..41e1aae635d8 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -1,8 +1,8 @@ -pub use data_models::payments::payment_attempt::{ - PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, -}; use diesel_models::{capture::CaptureNew, enums}; use error_stack::ResultExt; +pub use hyperswitch_domain_models::payments::payment_attempt::{ + PaymentAttempt, PaymentAttemptNew, PaymentAttemptUpdate, +}; use crate::{ core::errors, errors::RouterResult, types::transformers::ForeignFrom, utils::OptionExt, diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index b1b185eb92a5..ef12e32d57c9 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -315,30 +315,34 @@ impl ForeignFrom for api_models::payments::Man } // TODO: remove foreign from since this conversion won't be needed in the router crate once data models is treated as a single & primary source of truth for structure information -impl ForeignFrom for data_models::mandates::MandateData { +impl ForeignFrom + for hyperswitch_domain_models::mandates::MandateData +{ fn foreign_from(d: api_models::payments::MandateData) -> Self { Self { customer_acceptance: d.customer_acceptance.map(|d| { - data_models::mandates::CustomerAcceptance { + hyperswitch_domain_models::mandates::CustomerAcceptance { acceptance_type: match d.acceptance_type { api_models::payments::AcceptanceType::Online => { - data_models::mandates::AcceptanceType::Online + hyperswitch_domain_models::mandates::AcceptanceType::Online } api_models::payments::AcceptanceType::Offline => { - data_models::mandates::AcceptanceType::Offline + hyperswitch_domain_models::mandates::AcceptanceType::Offline } }, accepted_at: d.accepted_at, - online: d.online.map(|d| data_models::mandates::OnlineMandate { - ip_address: d.ip_address, - user_agent: d.user_agent, - }), + online: d + .online + .map(|d| hyperswitch_domain_models::mandates::OnlineMandate { + ip_address: d.ip_address, + user_agent: d.user_agent, + }), } }), mandate_type: d.mandate_type.map(|d| match d { api_models::payments::MandateType::MultiUse(Some(i)) => { - data_models::mandates::MandateDataType::MultiUse(Some( - data_models::mandates::MandateAmountData { + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(Some( + hyperswitch_domain_models::mandates::MandateAmountData { amount: i.amount, currency: i.currency, start_date: i.start_date, @@ -348,8 +352,8 @@ impl ForeignFrom for data_models::mandates::M )) } api_models::payments::MandateType::SingleUse(i) => { - data_models::mandates::MandateDataType::SingleUse( - data_models::mandates::MandateAmountData { + hyperswitch_domain_models::mandates::MandateDataType::SingleUse( + hyperswitch_domain_models::mandates::MandateAmountData { amount: i.amount, currency: i.currency, start_date: i.start_date, @@ -359,7 +363,7 @@ impl ForeignFrom for data_models::mandates::M ) } api_models::payments::MandateType::MultiUse(None) => { - data_models::mandates::MandateDataType::MultiUse(None) + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(None) } }), update_mandate_id: d.update_mandate_id, @@ -426,7 +430,8 @@ impl ForeignFrom for api_enums::PaymentMethod { | api_enums::PaymentMethodType::Gcash | api_enums::PaymentMethodType::Momo | api_enums::PaymentMethodType::Cashapp - | api_enums::PaymentMethodType::KakaoPay => Self::Wallet, + | api_enums::PaymentMethodType::KakaoPay + | api_enums::PaymentMethodType::Venmo => Self::Wallet, api_enums::PaymentMethodType::Affirm | api_enums::PaymentMethodType::Alma | api_enums::PaymentMethodType::AfterpayClearpay @@ -971,6 +976,7 @@ impl ForeignFrom for api_enums::PaymentMethodType { fn foreign_from(value: api_models::payouts::Wallet) -> Self { match value { api_models::payouts::Wallet::Paypal(_) => Self::Paypal, + api_models::payouts::Wallet::Venmo(_) => Self::Venmo, } } } diff --git a/crates/router/src/utils.rs b/crates/router/src/utils.rs index 2d4b34b30d59..341c560e4d2a 100644 --- a/crates/router/src/utils.rs +++ b/crates/router/src/utils.rs @@ -23,8 +23,8 @@ pub use common_utils::{ fp_utils::when, validation::validate_email, }; -use data_models::payments::PaymentIntent; use error_stack::ResultExt; +use hyperswitch_domain_models::payments::PaymentIntent; use image::Luma; use masking::ExposeInterface; use nanoid::nanoid; diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index 8469bf770d2b..2216cb105548 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -2,9 +2,9 @@ use api_models::{ enums::Connector::{DummyConnector4, DummyConnector7}, user::sample_data::SampleDataRequest, }; -use data_models::payments::payment_intent::PaymentIntentNew; use diesel_models::{user::sample_data::PaymentAttemptBatchNew, RefundNew}; use error_stack::ResultExt; +use hyperswitch_domain_models::payments::payment_intent::PaymentIntentNew; use rand::{prelude::SliceRandom, thread_rng, Rng}; use time::OffsetDateTime; @@ -187,7 +187,9 @@ pub async fn generate_sample_data( client_secret: Some(client_secret), business_country: business_country_default, business_label: business_label_default.clone(), - active_attempt: data_models::RemoteStorageObject::ForeignID(attempt_id.clone()), + active_attempt: hyperswitch_domain_models::RemoteStorageObject::ForeignID( + attempt_id.clone(), + ), attempt_count: 1, customer_id: Some("hs-dashboard-user".to_string()), amount_captured: Some(amount * 100), diff --git a/crates/router/src/workflows/payment_sync.rs b/crates/router/src/workflows/payment_sync.rs index 932a06bae6f6..5dbfff8342e6 100644 --- a/crates/router/src/workflows/payment_sync.rs +++ b/crates/router/src/workflows/payment_sync.rs @@ -123,9 +123,9 @@ impl ProcessTrackerWorkflow for PaymentsSyncWorkflow { .as_ref() .is_none() { - let payment_intent_update = data_models::payments::payment_intent::PaymentIntentUpdate::PGStatusUpdate { status: api_models::enums::IntentStatus::Failed,updated_by: merchant_account.storage_scheme.to_string(), incremental_authorization_allowed: Some(false) }; + let payment_intent_update = hyperswitch_domain_models::payments::payment_intent::PaymentIntentUpdate::PGStatusUpdate { status: api_models::enums::IntentStatus::Failed,updated_by: merchant_account.storage_scheme.to_string(), incremental_authorization_allowed: Some(false) }; let payment_attempt_update = - data_models::payments::payment_attempt::PaymentAttemptUpdate::ErrorUpdate { + hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ErrorUpdate { connector: None, status: api_models::enums::AttemptStatus::AuthenticationFailed, error_code: None, diff --git a/crates/router/tests/connectors/adyen.rs b/crates/router/tests/connectors/adyen.rs index 4ecf73d5339f..468834ca495e 100644 --- a/crates/router/tests/connectors/adyen.rs +++ b/crates/router/tests/connectors/adyen.rs @@ -122,6 +122,8 @@ impl AdyenTest { enums::PayoutType::Wallet => Some(types::api::PayoutMethodData::Wallet( types::api::payouts::WalletPayout::Paypal(api_models::payouts::Paypal { email: Email::from_str("EmailUsedForPayPalAccount@example.com").ok(), + telephone_number: None, + paypal_id: None, }), )), }, diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 7a990464da55..646a11d4cdd3 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -317,7 +317,7 @@ async fn payments_create_core() { setup_future_usage: Some(api_enums::FutureUsage::OnSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), @@ -329,7 +329,7 @@ async fn payments_create_core() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), @@ -499,7 +499,7 @@ async fn payments_create_core_adyen_no_redirect() { setup_future_usage: Some(api_enums::FutureUsage::OnSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), @@ -511,7 +511,7 @@ async fn payments_create_core_adyen_no_redirect() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index cce589fb0381..9b622d11fd1b 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -77,7 +77,7 @@ async fn payments_create_core() { setup_future_usage: None, authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "4242424242424242".to_string().try_into().unwrap(), card_exp_month: "10".to_string().into(), card_exp_year: "35".to_string().into(), @@ -89,7 +89,7 @@ async fn payments_create_core() { card_issuing_country: None, bank_code: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), payment_method: Some(api_enums::PaymentMethod::Card), @@ -266,7 +266,7 @@ async fn payments_create_core_adyen_no_redirect() { setup_future_usage: Some(api_enums::FutureUsage::OffSession), authentication_type: Some(api_enums::AuthenticationType::NoThreeDs), payment_method_data: Some(api::PaymentMethodDataRequest { - payment_method_data: api::PaymentMethodData::Card(api::Card { + payment_method_data: Some(api::PaymentMethodData::Card(api::Card { card_number: "5555 3412 4444 1115".to_string().try_into().unwrap(), card_exp_month: "03".to_string().into(), card_exp_year: "2030".to_string().into(), @@ -278,7 +278,7 @@ async fn payments_create_core_adyen_no_redirect() { card_type: None, card_issuing_country: None, nick_name: Some(masking::Secret::new("nick_name".into())), - }), + })), billing: None, }), diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index a7aac03db51b..26cb619d74cd 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -348,8 +348,10 @@ pub enum Flow { DeleteSampleData, /// List merchant accounts for user UserMerchantAccountList, - /// Get details of a user in a merchant account + /// Get details of a user GetUserDetails, + /// Get details of a user role in a merchant account + GetUserRoleDetails, /// List users for merchant account ListUsersForMerchantAccount, /// PaymentMethodAuth Link token create diff --git a/crates/storage_impl/Cargo.toml b/crates/storage_impl/Cargo.toml index 5421e301f6af..ab656c3f6583 100644 --- a/crates/storage_impl/Cargo.toml +++ b/crates/storage_impl/Cargo.toml @@ -11,14 +11,14 @@ license.workspace = true [features] default = ["olap", "oltp"] oltp = [] -olap = ["data_models/olap"] -payouts = ["data_models/payouts"] +olap = ["hyperswitch_domain_models/olap"] +payouts = ["hyperswitch_domain_models/payouts"] [dependencies] # First Party dependencies api_models = { version = "0.1.0", path = "../api_models" } common_utils = { version = "0.1.0", path = "../common_utils" } -data_models = { version = "0.1.0", path = "../data_models", default-features = false } +hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } diesel_models = { version = "0.1.0", path = "../diesel_models" } masking = { version = "0.1.0", path = "../masking" } redis_interface = { version = "0.1.0", path = "../redis_interface" } diff --git a/crates/storage_impl/src/database/store.rs b/crates/storage_impl/src/database/store.rs index 92ba8910bfbc..17ae76b5bb03 100644 --- a/crates/storage_impl/src/database/store.rs +++ b/crates/storage_impl/src/database/store.rs @@ -1,8 +1,8 @@ use async_bb8_diesel::{AsyncConnection, ConnectionError}; use bb8::CustomizeConnection; -use data_models::errors::{StorageError, StorageResult}; use diesel::PgConnection; use error_stack::ResultExt; +use hyperswitch_domain_models::errors::{StorageError, StorageResult}; use masking::PeekInterface; use crate::config::Database; diff --git a/crates/storage_impl/src/errors.rs b/crates/storage_impl/src/errors.rs index 4c5f84f5b734..41099bea75f8 100644 --- a/crates/storage_impl/src/errors.rs +++ b/crates/storage_impl/src/errors.rs @@ -3,8 +3,8 @@ use std::fmt::Display; use actix_web::ResponseError; use common_utils::errors::ErrorSwitch; use config::ConfigError; -use data_models::errors::StorageError as DataStorageError; use http::StatusCode; +use hyperswitch_domain_models::errors::StorageError as DataStorageError; pub use redis_interface::errors::RedisError; use router_env::opentelemetry::metrics::MetricsError; @@ -369,6 +369,8 @@ pub enum HealthCheckDBError { SqlxAnalyticsError, #[error("Error while executing query in Clickhouse Analytics")] ClickhouseAnalyticsError, + #[error("Error while executing query in Opensearch")] + OpensearchError, } impl From for HealthCheckDBError { diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index f139641a5656..e35d1f415110 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -1,8 +1,8 @@ use std::sync::Arc; -use data_models::errors::{StorageError, StorageResult}; use diesel_models as store; use error_stack::ResultExt; +use hyperswitch_domain_models::errors::{StorageError, StorageResult}; use masking::StrongSecret; use redis::{kv_store::RedisConnInterface, RedisStore}; mod address; @@ -25,9 +25,9 @@ mod reverse_lookup; mod utils; use common_utils::errors::CustomResult; -#[cfg(not(feature = "payouts"))] -use data_models::{PayoutAttemptInterface, PayoutsInterface}; use database::store::PgPool; +#[cfg(not(feature = "payouts"))] +use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; pub use mock_db::MockDb; use redis_interface::{errors::RedisError, SaddReply}; diff --git a/crates/storage_impl/src/lookup.rs b/crates/storage_impl/src/lookup.rs index fb8c86644365..f8daabf2e9eb 100644 --- a/crates/storage_impl/src/lookup.rs +++ b/crates/storage_impl/src/lookup.rs @@ -1,5 +1,4 @@ use common_utils::errors::CustomResult; -use data_models::errors; use diesel_models::{ enums as storage_enums, kv, reverse_lookup::{ @@ -7,6 +6,7 @@ use diesel_models::{ }, }; use error_stack::ResultExt; +use hyperswitch_domain_models::errors; use redis_interface::SetnxReply; use crate::{ diff --git a/crates/storage_impl/src/mock_db.rs b/crates/storage_impl/src/mock_db.rs index 77cda9817a35..5cf0123eb7ba 100644 --- a/crates/storage_impl/src/mock_db.rs +++ b/crates/storage_impl/src/mock_db.rs @@ -1,12 +1,12 @@ use std::sync::Arc; -use data_models::{ - errors::StorageError, - payments::{payment_attempt::PaymentAttempt, PaymentIntent}, -}; use diesel_models::{self as store}; use error_stack::ResultExt; use futures::lock::Mutex; +use hyperswitch_domain_models::{ + errors::StorageError, + payments::{payment_attempt::PaymentAttempt, PaymentIntent}, +}; use redis_interface::RedisSettings; use crate::redis::RedisStore; @@ -19,7 +19,7 @@ pub mod payout_attempt; pub mod payouts; pub mod redis_conn; #[cfg(not(feature = "payouts"))] -use data_models::{PayoutAttemptInterface, PayoutsInterface}; +use hyperswitch_domain_models::{PayoutAttemptInterface, PayoutsInterface}; #[derive(Clone)] pub struct MockDb { diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 996c8e102b72..33ef6d416f23 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -1,12 +1,12 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; use common_utils::errors::CustomResult; -use data_models::{ +use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::{ errors::StorageError, payments::payment_attempt::{ PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, }, }; -use diesel_models::enums as storage_enums; use super::MockDb; use crate::DataModelExt; @@ -26,11 +26,13 @@ impl PaymentAttemptInterface for MockDb { async fn get_filters_for_payments( &self, - _pi: &[data_models::payments::PaymentIntent], + _pi: &[hyperswitch_domain_models::payments::PaymentIntent], _merchant_id: &str, _storage_scheme: storage_enums::MerchantStorageScheme, - ) -> CustomResult - { + ) -> CustomResult< + hyperswitch_domain_models::payments::payment_attempt::PaymentListFilters, + StorageError, + > { Err(StorageError::MockDbError)? } diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index a693eb5e2613..1dfb7ac9d91e 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -1,5 +1,7 @@ use common_utils::errors::CustomResult; -use data_models::{ +use diesel_models::enums as storage_enums; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ errors::StorageError, payments::{ payment_attempt::PaymentAttempt, @@ -7,8 +9,6 @@ use data_models::{ PaymentIntent, }, }; -use diesel_models::enums as storage_enums; -use error_stack::ResultExt; use super::MockDb; use crate::DataModelExt; @@ -19,7 +19,7 @@ impl PaymentIntentInterface for MockDb { async fn filter_payment_intent_by_constraints( &self, _merchant_id: &str, - _filters: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _filters: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, StorageError> { // [#172]: Implement function for `MockDb` @@ -39,7 +39,7 @@ impl PaymentIntentInterface for MockDb { async fn get_filtered_active_attempt_ids_for_total_count( &self, _merchant_id: &str, - _constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, StorageError> { // [#172]: Implement function for `MockDb` @@ -49,7 +49,7 @@ impl PaymentIntentInterface for MockDb { async fn get_filtered_payment_intents_attempt( &self, _merchant_id: &str, - _constraints: &data_models::payments::payment_intent::PaymentIntentFetchConstraints, + _constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result, StorageError> { // [#172]: Implement function for `MockDb` @@ -159,7 +159,7 @@ impl PaymentIntentInterface for MockDb { _storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result { match payment.active_attempt.clone() { - data_models::RemoteStorageObject::ForeignID(id) => { + hyperswitch_domain_models::RemoteStorageObject::ForeignID(id) => { let attempts = self.payment_attempts.lock().await; let attempt = attempts .iter() @@ -169,7 +169,7 @@ impl PaymentIntentInterface for MockDb { payment.active_attempt = attempt.clone().into(); Ok(attempt.clone()) } - data_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), + hyperswitch_domain_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), } } } diff --git a/crates/storage_impl/src/mock_db/payout_attempt.rs b/crates/storage_impl/src/mock_db/payout_attempt.rs index 6b792e033d19..8f26b8c58104 100644 --- a/crates/storage_impl/src/mock_db/payout_attempt.rs +++ b/crates/storage_impl/src/mock_db/payout_attempt.rs @@ -1,5 +1,6 @@ use common_utils::errors::CustomResult; -use data_models::{ +use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::{ errors::StorageError, payouts::{ payout_attempt::{ @@ -8,7 +9,6 @@ use data_models::{ payouts::Payouts, }, }; -use diesel_models::enums as storage_enums; use super::MockDb; @@ -50,7 +50,10 @@ impl PayoutAttemptInterface for MockDb { _payouts: &[Payouts], _merchant_id: &str, _storage_scheme: storage_enums::MerchantStorageScheme, - ) -> CustomResult { + ) -> CustomResult< + hyperswitch_domain_models::payouts::payout_attempt::PayoutListFilters, + StorageError, + > { Err(StorageError::MockDbError)? } } diff --git a/crates/storage_impl/src/mock_db/payouts.rs b/crates/storage_impl/src/mock_db/payouts.rs index 1aee200eed0b..04e1321f1eb0 100644 --- a/crates/storage_impl/src/mock_db/payouts.rs +++ b/crates/storage_impl/src/mock_db/payouts.rs @@ -1,12 +1,12 @@ use common_utils::errors::CustomResult; -use data_models::{ +use diesel_models::enums as storage_enums; +use hyperswitch_domain_models::{ errors::StorageError, payouts::{ payout_attempt::PayoutAttempt, payouts::{Payouts, PayoutsInterface, PayoutsNew, PayoutsUpdate}, }, }; -use diesel_models::enums as storage_enums; use super::MockDb; @@ -56,7 +56,7 @@ impl PayoutsInterface for MockDb { async fn filter_payouts_by_constraints( &self, _merchant_id: &str, - _filters: &data_models::payouts::PayoutFetchConstraints, + _filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, StorageError> { // TODO: Implement function for `MockDb` @@ -67,7 +67,7 @@ impl PayoutsInterface for MockDb { async fn filter_payouts_and_attempts( &self, _merchant_id: &str, - _filters: &data_models::payouts::PayoutFetchConstraints, + _filters: &hyperswitch_domain_models::payouts::PayoutFetchConstraints, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult, StorageError> { // TODO: Implement function for `MockDb` diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 683b0469a2aa..4308e463b0c7 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1,16 +1,5 @@ use api_models::enums::{AuthenticationType, Connector, PaymentMethod, PaymentMethodType}; use common_utils::{errors::CustomResult, fallback_reverse_lookup_not_found}; -use data_models::{ - errors, - mandates::{MandateAmountData, MandateDataType, MandateDetails}, - payments::{ - payment_attempt::{ - PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, - PaymentListFilters, - }, - PaymentIntent, - }, -}; use diesel_models::{ enums::{ MandateAmountData as DieselMandateAmountData, MandateDataType as DieselMandateType, @@ -24,6 +13,17 @@ use diesel_models::{ reverse_lookup::{ReverseLookup, ReverseLookupNew}, }; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + errors, + mandates::{MandateAmountData, MandateDataType, MandateDetails}, + payments::{ + payment_attempt::{ + PaymentAttempt, PaymentAttemptInterface, PaymentAttemptNew, PaymentAttemptUpdate, + PaymentListFilters, + }, + PaymentIntent, + }, +}; use redis_interface::HsetnxReply; use router_env::{instrument, tracing}; diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 4d984a02cc96..fcc4ec63ff2c 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -6,17 +6,6 @@ use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; use common_utils::errors::ReportSwitchExt; use common_utils::{date_time, ext_traits::Encode}; #[cfg(feature = "olap")] -use data_models::payments::payment_intent::PaymentIntentFetchConstraints; -use data_models::{ - errors::StorageError, - payments::{ - payment_attempt::PaymentAttempt, - payment_intent::{PaymentIntentInterface, PaymentIntentNew, PaymentIntentUpdate}, - PaymentIntent, - }, - RemoteStorageObject, -}; -#[cfg(feature = "olap")] use diesel::{associations::HasTable, ExpressionMethods, JoinOnDsl, QueryDsl}; use diesel_models::{ enums::MerchantStorageScheme, @@ -33,6 +22,17 @@ use diesel_models::{ schema::{payment_attempt::dsl as pa_dsl, payment_intent::dsl as pi_dsl}, }; use error_stack::ResultExt; +#[cfg(feature = "olap")] +use hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints; +use hyperswitch_domain_models::{ + errors::StorageError, + payments::{ + payment_attempt::PaymentAttempt, + payment_intent::{PaymentIntentInterface, PaymentIntentNew, PaymentIntentUpdate}, + PaymentIntent, + }, + RemoteStorageObject, +}; use redis_interface::HsetnxReply; #[cfg(feature = "olap")] use router_env::logger; @@ -257,7 +257,7 @@ impl PaymentIntentInterface for KVRouterStore { _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { match payment.active_attempt.clone() { - data_models::RemoteStorageObject::ForeignID(attempt_id) => { + hyperswitch_domain_models::RemoteStorageObject::ForeignID(attempt_id) => { let conn = pg_connection_read(self).await?; let pa = DieselPaymentAttempt::find_by_merchant_id_attempt_id( @@ -271,10 +271,11 @@ impl PaymentIntentInterface for KVRouterStore { er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model)?; - payment.active_attempt = data_models::RemoteStorageObject::Object(pa.clone()); + payment.active_attempt = + hyperswitch_domain_models::RemoteStorageObject::Object(pa.clone()); Ok(pa) } - data_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), + hyperswitch_domain_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), } } @@ -396,7 +397,7 @@ impl PaymentIntentInterface for crate::RouterStore { _storage_scheme: MerchantStorageScheme, ) -> error_stack::Result { match &payment.active_attempt { - data_models::RemoteStorageObject::ForeignID(attempt_id) => { + hyperswitch_domain_models::RemoteStorageObject::ForeignID(attempt_id) => { let conn = pg_connection_read(self).await?; let pa = DieselPaymentAttempt::find_by_merchant_id_attempt_id( @@ -410,10 +411,11 @@ impl PaymentIntentInterface for crate::RouterStore { er.change_context(new_err) }) .map(PaymentAttempt::from_storage_model)?; - payment.active_attempt = data_models::RemoteStorageObject::Object(pa.clone()); + payment.active_attempt = + hyperswitch_domain_models::RemoteStorageObject::Object(pa.clone()); Ok(pa) } - data_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), + hyperswitch_domain_models::RemoteStorageObject::Object(pa) => Ok(pa.clone()), } } diff --git a/crates/storage_impl/src/payouts/payout_attempt.rs b/crates/storage_impl/src/payouts/payout_attempt.rs index 50334442e634..6a62832e1bf9 100644 --- a/crates/storage_impl/src/payouts/payout_attempt.rs +++ b/crates/storage_impl/src/payouts/payout_attempt.rs @@ -2,16 +2,6 @@ use std::str::FromStr; use api_models::enums::PayoutConnectors; use common_utils::{errors::CustomResult, ext_traits::Encode, fallback_reverse_lookup_not_found}; -use data_models::{ - errors, - payouts::{ - payout_attempt::{ - PayoutAttempt, PayoutAttemptInterface, PayoutAttemptNew, PayoutAttemptUpdate, - PayoutListFilters, - }, - payouts::Payouts, - }, -}; use diesel_models::{ enums::MerchantStorageScheme, kv, @@ -22,6 +12,16 @@ use diesel_models::{ ReverseLookupNew, }; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + errors, + payouts::{ + payout_attempt::{ + PayoutAttempt, PayoutAttemptInterface, PayoutAttemptNew, PayoutAttemptUpdate, + PayoutListFilters, + }, + payouts::Payouts, + }, +}; use redis_interface::HsetnxReply; use router_env::{instrument, logger, tracing}; diff --git a/crates/storage_impl/src/payouts/payouts.rs b/crates/storage_impl/src/payouts/payouts.rs index 0b1669467582..19921121a94d 100644 --- a/crates/storage_impl/src/payouts/payouts.rs +++ b/crates/storage_impl/src/payouts/payouts.rs @@ -2,15 +2,6 @@ use async_bb8_diesel::{AsyncConnection, AsyncRunQueryDsl}; use common_utils::ext_traits::Encode; #[cfg(feature = "olap")] -use data_models::payouts::PayoutFetchConstraints; -use data_models::{ - errors::StorageError, - payouts::{ - payout_attempt::PayoutAttempt, - payouts::{Payouts, PayoutsInterface, PayoutsNew, PayoutsUpdate}, - }, -}; -#[cfg(feature = "olap")] use diesel::{associations::HasTable, ExpressionMethods, JoinOnDsl, QueryDsl}; #[cfg(feature = "olap")] use diesel_models::{ @@ -28,6 +19,15 @@ use diesel_models::{ }, }; use error_stack::ResultExt; +#[cfg(feature = "olap")] +use hyperswitch_domain_models::payouts::PayoutFetchConstraints; +use hyperswitch_domain_models::{ + errors::StorageError, + payouts::{ + payout_attempt::PayoutAttempt, + payouts::{Payouts, PayoutsInterface, PayoutsNew, PayoutsUpdate}, + }, +}; use redis_interface::HsetnxReply; #[cfg(feature = "olap")] use router_env::logger; diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index a960261f8674..dc2a2225dc91 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -4,9 +4,9 @@ use common_utils::{ errors::{self, CustomResult}, ext_traits::AsyncExt, }; -use data_models::errors::StorageError; use dyn_clone::DynClone; use error_stack::{Report, ResultExt}; +use hyperswitch_domain_models::errors::StorageError; use moka::future::Cache as MokaCache; use once_cell::sync::Lazy; use redis_interface::{errors::RedisError, RedisValue}; diff --git a/crates/storage_impl/src/utils.rs b/crates/storage_impl/src/utils.rs index 78198e927532..b634f41a98f1 100644 --- a/crates/storage_impl/src/utils.rs +++ b/crates/storage_impl/src/utils.rs @@ -1,7 +1,7 @@ use bb8::PooledConnection; -use data_models::errors::StorageError; use diesel::PgConnection; use error_stack::ResultExt; +use hyperswitch_domain_models::errors::StorageError; use crate::{errors::RedisErrorExt, metrics, DatabaseStore}; diff --git a/docker-compose.yml b/docker-compose.yml index e55008f1e34e..040832f8e27a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -332,4 +332,35 @@ services: ulimits: nofile: soft: 262144 - hard: 262144 \ No newline at end of file + hard: 262144 + + fluentd: + build: ./docker/fluentd + volumes: + - ./docker/fluentd/conf:/fluentd/etc + networks: + - router_net + + opensearch: + image: public.ecr.aws/opensearchproject/opensearch:1.3.14 + container_name: opensearch + hostname: opensearch + environment: + - "discovery.type=single-node" + expose: + - "9200" + ports: + - "9200:9200" + networks: + - router_net + + opensearch-dashboards: + image: opensearchproject/opensearch-dashboards:1.3.14 + ports: + - 5601:5601 + expose: + - "5601" + environment: + OPENSEARCH_HOSTS: '["https://opensearch:9200"]' + networks: + - router_net \ No newline at end of file diff --git a/docker/fluentd/Dockerfile b/docker/fluentd/Dockerfile new file mode 100644 index 000000000000..5c85269e1a60 --- /dev/null +++ b/docker/fluentd/Dockerfile @@ -0,0 +1,7 @@ +# docker/fluentd/Dockerfile + +FROM fluent/fluentd:v1.16-debian-2 +USER root +RUN ["gem", "install", "fluent-plugin-kafka", "--no-document"] +RUN ["gem", "install", "fluent-plugin-opensearch", "--no-document"] +USER fluent diff --git a/docker/fluentd/conf/fluent.conf b/docker/fluentd/conf/fluent.conf new file mode 100644 index 000000000000..7aec9dd7cd8e --- /dev/null +++ b/docker/fluentd/conf/fluent.conf @@ -0,0 +1,137 @@ +# docker/fluentd/conf/fluent.conf + + + @type kafka_group + + brokers kafka0:29092 + consumer_group fluentd + topics hyperswitch-payment-intent-events,hyperswitch-payment-attempt-events,hyperswitch-refund-events,hyperswitch-dispute-events + add_headers false + add_prefix topic + retry_emit_limit 2 + + + + @type record_transformer + renew_time_key created_at + + + + @type record_transformer + renew_time_key created_at + + + + @type record_transformer + renew_time_key created_at + + + + + @type record_transformer + renew_time_key created_at + + + + @type copy + + + @type stdout + + + + @type opensearch + host opensearch + port 9200 + scheme https + index_name hyperswitch-payment-intent-events + id_key payment_id + user admin + password admin + ssl_verify false + prefer_oj_serializer true + reload_on_failure true + reload_connections false + request_timeout 120s + bulk_message_request_threshold 10MB + include_timestamp true + + + + + @type copy + + + @type stdout + + + + @type opensearch + host opensearch + port 9200 + scheme https + index_name hyperswitch-payment-attempt-events + id_key attempt_id + user admin + password admin + ssl_verify false + prefer_oj_serializer true + reload_on_failure true + reload_connections false + request_timeout 120s + bulk_message_request_threshold 10MB + include_timestamp true + + + + + @type copy + + + @type stdout + + + + @type opensearch + host opensearch + port 9200 + scheme https + index_name hyperswitch-refund-events + id_key refund_id + user admin + password admin + ssl_verify false + prefer_oj_serializer true + reload_on_failure true + reload_connections false + request_timeout 120s + bulk_message_request_threshold 10MB + include_timestamp true + + + + + @type copy + + + @type stdout + + + + @type opensearch + host opensearch + port 9200 + scheme https + index_name hyperswitch-dispute-events + id_key dispute_id + user admin + password admin + ssl_verify false + prefer_oj_serializer true + reload_on_failure true + reload_connections false + request_timeout 120s + bulk_message_request_threshold 10MB + include_timestamp true + + \ No newline at end of file diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 42ea3e545597..d278c50a388e 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -8711,6 +8711,86 @@ "mandate_revoked" ] }, + "ExtendedCardInfo": { + "type": "object", + "required": [ + "card_number", + "card_exp_month", + "card_exp_year", + "card_holder_name" + ], + "properties": { + "card_number": { + "type": "string", + "description": "The card number", + "example": "4242424242424242" + }, + "card_exp_month": { + "type": "string", + "description": "The card's expiry month", + "example": "24" + }, + "card_exp_year": { + "type": "string", + "description": "The card's expiry year", + "example": "24" + }, + "card_holder_name": { + "type": "string", + "description": "The card holder's name", + "example": "John Test" + }, + "card_issuer": { + "type": "string", + "description": "The name of the issuer of card", + "example": "chase", + "nullable": true + }, + "card_network": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + }, + "card_type": { + "type": "string", + "example": "CREDIT", + "nullable": true + }, + "card_issuing_country": { + "type": "string", + "example": "INDIA", + "nullable": true + }, + "bank_code": { + "type": "string", + "example": "JP_AMEX", + "nullable": true + } + } + }, + "ExtendedCardInfoConfig": { + "type": "object", + "required": [ + "public_key" + ], + "properties": { + "public_key": { + "type": "string", + "description": "Merchant public key" + }, + "ttl_in_secs": { + "type": "integer", + "format": "int32", + "description": "TTL for extended card info", + "default": 900, + "maximum": 3600, + "minimum": 0 + } + } + }, "ExtendedCardInfoResponse": { "type": "object", "required": [ @@ -12707,7 +12787,12 @@ "PaymentMethodDataRequest": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodData" + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodData" + } + ], + "nullable": true }, { "type": "object", @@ -13023,6 +13108,7 @@ "twint", "upi_collect", "vipps", + "venmo", "walley", "we_chat_pay", "seven_eleven", @@ -16287,13 +16373,25 @@ "Paypal": { "type": "object", "required": [ - "email" + "email", + "telephone_number", + "paypal_id" ], "properties": { "email": { "type": "string", "description": "Email linked with paypal account", "example": "john.doe@example.com" + }, + "telephone_number": { + "type": "string", + "description": "mobile number linked to paypal account", + "example": "16608213349" + }, + "paypal_id": { + "type": "string", + "description": "id of the paypal account", + "example": "G83KXTJ5EHCQ2" } } }, @@ -18294,6 +18392,19 @@ "propertyName": "type" } }, + "Venmo": { + "type": "object", + "required": [ + "telephone_number" + ], + "properties": { + "telephone_number": { + "type": "string", + "description": "mobile number linked to venmo account", + "example": "16608213349" + } + } + }, "VoucherData": { "oneOf": [ { @@ -18439,6 +18550,17 @@ "$ref": "#/components/schemas/Paypal" } } + }, + { + "type": "object", + "required": [ + "venmo" + ], + "properties": { + "venmo": { + "$ref": "#/components/schemas/Venmo" + } + } } ] }, diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/.meta.json index 9339372cc716..c5e687f68c92 100644 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/.meta.json +++ b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/.meta.json @@ -22,9 +22,8 @@ "Scenario15-Incremental auth with partial capture", "Scenario16-Verify PML for mandate", "Scenario17-Revoke mandates", - "Scenario18-Update mandate card details", - "Scenario19-Create 3ds payment", - "Scenario20-Create 3ds mandate", - "Scenario21-Create a mandate without customer acceptance" + "Scenario18-Create 3ds payment", + "Scenario19-Create 3ds mandate", + "Scenario20-Create a mandate without customer acceptance" ] } diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Create 3ds payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/.meta.json deleted file mode 100644 index 20ac234bf998..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/.meta.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "childrenOrder": [ - "Payments - Create", - "Payments - Retrieve", - "List - Mandates", - "Mandate - Update", - "List - Mandates-copy", - "Recurring Payments - Create", - "Payments - Retrieve-copy" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/event.test.js deleted file mode 100644 index 0d07a384aede..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/event.test.js +++ /dev/null @@ -1,46 +0,0 @@ -// Validate status 2xx -pm.test("[GET]::/payments/:id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - - -// Response body should have value "active" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'active'", - function () { - pm.expect(jsonData.status).to.eql("active"); - }, - ); -} - -pm.test("[POST]::/payments - Verify last 4 digits of the card", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.last4_digits).to.eql("1111"); -}); - -pm.test("[POST]::/payments - Verify card expiration month", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.card_exp_month).to.eql("12"); -}); - -pm.test("[POST]::/payments - Verify card expiration year", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.card_exp_year).to.eql("30"); -}); - - - diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/request.json deleted file mode 100644 index 349b5117443f..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates-copy/request.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/mandates/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "mandates", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{mandate_id}}", - "description": "(Required) Unique mandate id" - } - ] - }, - "description": "To list the details of a mandate" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/event.test.js deleted file mode 100644 index 3a7c6c4468ba..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/event.test.js +++ /dev/null @@ -1,46 +0,0 @@ -// Validate status 2xx -pm.test("[GET]::/payments/:id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - - -// Response body should have value "active" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'active'", - function () { - pm.expect(jsonData.status).to.eql("active"); - }, - ); -} - -pm.test("[POST]::/payments - Verify last 4 digits of the card", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.last4_digits).to.eql("4242"); -}); - -pm.test("[POST]::/payments - Verify card expiration month", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.card_exp_month).to.eql("10"); -}); - -pm.test("[POST]::/payments - Verify card expiration year", function () { - var jsonData = pm.response.json(); - pm.expect(jsonData.card.card_exp_year).to.eql("25"); -}); - - - diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/request.json deleted file mode 100644 index 349b5117443f..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/List - Mandates/request.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/mandates/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "mandates", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{mandate_id}}", - "description": "(Required) Unique mandate id" - } - ] - }, - "description": "To list the details of a mandate" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/event.test.js deleted file mode 100644 index 13d57327d210..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/event.test.js +++ /dev/null @@ -1,80 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/payments - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[POST]::/payments - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[POST]::/payments - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id -if (jsonData?.mandate_id) { - pm.collectionVariables.set("mandate_id", jsonData.mandate_id); - console.log( - "- use {{mandate_id}} as collection variable for value", - jsonData.mandate_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have "mandate_id" -pm.test( - "[POST]::/payments - Content check if 'mandate_id' exists", - function () { - pm.expect(typeof jsonData.mandate_id !== "undefined").to.be.true; - }, -); - diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/request.json deleted file mode 100644 index b8a76d3412a5..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/request.json +++ /dev/null @@ -1,102 +0,0 @@ -{ - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": { - "amount": 0, - "currency": "USD", - "confirm": true, - "capture_method": "automatic", - "capture_on": "2022-09-10T10:11:12Z", - "customer_id": "{{customer_id}}", - "email": "guest@example.com", - "name": "John Doe", - "phone": "999999999", - "phone_country_code": "+65", - "description": "Its my first payment request", - "authentication_type": "no_three_ds", - "return_url": "https://duck.com", - "payment_method": "card", - "payment_method_type": "debit", - "payment_method_data": { - "card": { - "card_number": "4111111111111111", - "card_exp_month": "12", - "card_exp_year": "30", - "card_holder_name": "joseph Doe", - "card_cvc": "123" - } - }, - "payment_type": "setup_mandate", - "setup_future_usage": "off_session", - "mandate_data": { - "update_mandate_id": "{{mandate_id}}", - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - } - }, - "shipping": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "billing": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "statement_descriptor_name": "joseph", - "statement_descriptor_suffix": "JS", - "metadata": { - "count_tickets": 1, - "transaction_number": "5590045" - } - } - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/event.test.js deleted file mode 100644 index 29cba9891864..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/event.test.js +++ /dev/null @@ -1,109 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/payments - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[POST]::/payments - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[POST]::/payments - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id -if (jsonData?.mandate_id) { - pm.collectionVariables.set("mandate_id", jsonData.mandate_id); - console.log( - "- use {{mandate_id}} as collection variable for value", - jsonData.mandate_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have value "succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have "mandate_id" -pm.test( - "[POST]::/payments - Content check if 'mandate_id' exists", - function () { - pm.expect(typeof jsonData.mandate_id !== "undefined").to.be.true; - }, -); - -// Response body should have "mandate_data" -pm.test( - "[POST]::/payments - Content check if 'mandate_data' exists", - function () { - pm.expect(typeof jsonData.mandate_data !== "undefined").to.be.true; - }, -); - -if (jsonData?.customer_id) { - pm.collectionVariables.set("customer_id", jsonData.customer_id); - console.log( - "- use {{customer_id}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.", - ); -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/request.json deleted file mode 100644 index 4a6989971692..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/request.json +++ /dev/null @@ -1,106 +0,0 @@ -{ - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": { - "amount": 0, - "currency": "USD", - "confirm": true, - "capture_method": "automatic", - "capture_on": "2022-09-10T10:11:12Z", - "customer_id": "customer_{{$randomAbbreviation}}", - "email": "guest@example.com", - "name": "John Doe", - "phone": "999999999", - "phone_country_code": "+65", - "description": "Its my first payment request", - "authentication_type": "no_three_ds", - "return_url": "https://duck.com", - "payment_method": "card", - "payment_method_data": { - "card": { - "card_number": "4242424242424242", - "card_exp_month": "10", - "card_exp_year": "25", - "card_holder_name": "joseph Doe", - "card_cvc": "123" - } - }, - "payment_type": "setup_mandate", - "setup_future_usage": "off_session", - "mandate_data": { - "customer_acceptance": { - "acceptance_type": "offline", - "accepted_at": "1963-05-03T04:07:52.723Z", - "online": { - "ip_address": "127.0.0.1", - "user_agent": "amet irure esse" - } - }, - "mandate_type": { - "single_use": { - "amount": 7000, - "currency": "USD" - } - } - }, - "shipping": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "billing": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "statement_descriptor_name": "joseph", - "statement_descriptor_suffix": "JS", - "metadata": { - "count_tickets": 1, - "transaction_number": "5590045" - } - } - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/event.test.js deleted file mode 100644 index 4d0a9b9ca2cf..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/event.test.js +++ /dev/null @@ -1,87 +0,0 @@ -// Validate status 2xx -pm.test("[GET]::/payments/:id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[GET]::/payments/:id - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id -if (jsonData?.mandate_id) { - pm.collectionVariables.set("mandate_id", jsonData.mandate_id); - console.log( - "- use {{mandate_id}} as collection variable for value", - jsonData.mandate_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", - ); -} - -// Response body should have value "Succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have "mandate_id" -pm.test( - "[POST]::/payments - Content check if 'mandate_id' exists", - function () { - pm.expect(typeof jsonData.mandate_id !== "undefined").to.be.true; - }, -); - -// Response body should have "mandate_data" -pm.test( - "[POST]::/payments - Content check if 'mandate_data' exists", - function () { - pm.expect(typeof jsonData.mandate_data !== "undefined").to.be.true; - }, -); diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Mandate - Update/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds mandate/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/event.prerequest.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/event.prerequest.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/event.prerequest.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/event.prerequest.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Confirm/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Payments - Retrieve-copy/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve-copy/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create 3ds mandate/Payments - Retrieve/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Payments - Retrieve/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Create/.event.meta.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/.event.meta.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/event.test.js rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/event.test.js diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/request.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario18-Update mandate card details/Recurring Payments - Create/request.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/request.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/response.json similarity index 100% rename from postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario19-Create 3ds payment/Payments - Retrieve/response.json rename to postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario20-Create a mandate without customer acceptance/Recurring Payments - Create/response.json diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Confirm/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/event.test.js deleted file mode 100644 index fe8fa706b96f..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/event.test.js +++ /dev/null @@ -1,75 +0,0 @@ -// Validate status 2xx -pm.test("[GET]::/payments/:id - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[GET]::/payments/:id - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[GET]::/payments/:id - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "Succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have "mandate_id" -pm.test( - "[POST]::/payments - Content check if 'mandate_id' exists", - function () { - pm.expect(typeof jsonData.mandate_id !== "undefined").to.be.true; - }, -); - -// Response body should have "mandate_data" -pm.test( - "[POST]::/payments - Content check if 'mandate_data' exists", - function () { - pm.expect(typeof jsonData.mandate_data !== "undefined").to.be.true; - }, -); diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/request.json deleted file mode 100644 index b9ebc1be4aa3..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve-copy/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/request.json deleted file mode 100644 index b9ebc1be4aa3..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/request.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Payments - Retrieve/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/.event.meta.json deleted file mode 100644 index 688c85746ef1..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/.event.meta.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "eventOrder": [ - "event.test.js" - ] -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/event.test.js b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/event.test.js deleted file mode 100644 index e2ddde7136f8..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/event.test.js +++ /dev/null @@ -1,95 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/payments - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test("[POST]::/payments - Content-Type is application/json", function () { - pm.expect(pm.response.headers.get("Content-Type")).to.include( - "application/json", - ); -}); - -// Validate if response has JSON Body -pm.test("[POST]::/payments - Response has JSON Body", function () { - pm.response.to.have.jsonBody(); -}); - -// Set response object as internal variable -let jsonData = {}; -try { - jsonData = pm.response.json(); -} catch (e) {} - -// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id -if (jsonData?.payment_id) { - pm.collectionVariables.set("payment_id", jsonData.payment_id); - console.log( - "- use {{payment_id}} as collection variable for value", - jsonData.payment_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.", - ); -} - -// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id -if (jsonData?.mandate_id) { - pm.collectionVariables.set("mandate_id", jsonData.mandate_id); - console.log( - "- use {{mandate_id}} as collection variable for value", - jsonData.mandate_id, - ); -} else { - console.log( - "INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.", - ); -} - -// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret -if (jsonData?.client_secret) { - pm.collectionVariables.set("client_secret", jsonData.client_secret); - console.log( - "- use {{client_secret}} as collection variable for value", - jsonData.client_secret, - ); -} else { - console.log( - "INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.", - ); -} - -// Response body should have value "succeeded" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'succeeded'", - function () { - pm.expect(jsonData.status).to.eql("succeeded"); - }, - ); -} - -// Response body should have "mandate_id" -pm.test( - "[POST]::/payments - Content check if 'mandate_id' exists", - function () { - pm.expect(typeof jsonData.mandate_id !== "undefined").to.be.true; - }, -); - -// Response body should have "mandate_data" -pm.test( - "[POST]::/payments - Content check if 'mandate_data' exists", - function () { - pm.expect(typeof jsonData.mandate_data !== "undefined").to.be.true; - }, -); - -// Response body should have "payment_method_data" -pm.test( - "[POST]::/payments - Content check if 'payment_method_data' exists", - function () { - pm.expect(typeof jsonData.payment_method_data !== "undefined").to.be.true; - }, -); diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/request.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/request.json deleted file mode 100644 index 0ae243421f63..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/request.json +++ /dev/null @@ -1,77 +0,0 @@ -{ - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": { - "amount": 6540, - "currency": "USD", - "confirm": true, - "capture_method": "automatic", - "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 6540, - "customer_id": "{{customer_id}}", - "email": "guest@example.com", - "name": "John Doe", - "phone": "999999999", - "phone_country_code": "+65", - "description": "Its my first payment request", - "authentication_type": "no_three_ds", - "return_url": "https://duck.com", - "mandate_id": "{{mandate_id}}", - "off_session": true, - "shipping": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "billing": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "likhin", - "last_name": "bopanna" - } - }, - "statement_descriptor_name": "joseph", - "statement_descriptor_suffix": "JS" - } - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" -} diff --git a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/response.json b/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/response.json deleted file mode 100644 index fe51488c7066..000000000000 --- a/postman/collection-dir/cybersource/Flow Testcases/Happy Cases/Scenario21-Create a mandate without customer acceptance/Recurring Payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-json/cybersource.postman_collection.json b/postman/collection-json/cybersource.postman_collection.json index 630020dab93a..128789666aa5 100644 --- a/postman/collection-json/cybersource.postman_collection.json +++ b/postman/collection-json/cybersource.postman_collection.json @@ -11927,871 +11927,7 @@ ] }, { - "name": "Scenario18-Update mandate card details", - "item": [ - { - "name": "Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "", - "if (jsonData?.customer_id) {", - " pm.collectionVariables.set(\"customer_id\", jsonData.customer_id);", - " console.log(", - " \"- use {{customer_id}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{customer_id}}, as jsonData.customer_id is undefined.\",", - " );", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":0,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"customer_{{$randomAbbreviation}}\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"payment_type\":\"setup_mandate\",\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}},\"mandate_type\":{\"single_use\":{\"amount\":7000,\"currency\":\"USD\"}}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"count_tickets\":1,\"transaction_number\":\"5590045\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Retrieve", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"Succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" - }, - "response": [] - }, - { - "name": "List - Mandates", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "", - "// Response body should have value \"active\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'active'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"active\");", - " },", - " );", - "}", - "", - "pm.test(\"[POST]::/payments - Verify last 4 digits of the card\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.last4_digits).to.eql(\"4242\");", - "});", - "", - "pm.test(\"[POST]::/payments - Verify card expiration month\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.card_exp_month).to.eql(\"10\");", - "});", - "", - "pm.test(\"[POST]::/payments - Verify card expiration year\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.card_exp_year).to.eql(\"25\");", - "});", - "", - "", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/mandates/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "mandates", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{mandate_id}}", - "description": "(Required) Unique mandate id" - } - ] - }, - "description": "To list the details of a mandate" - }, - "response": [] - }, - { - "name": "Mandate - Update", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":0,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"{{customer_id}}\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4111111111111111\",\"card_exp_month\":\"12\",\"card_exp_year\":\"30\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"payment_type\":\"setup_mandate\",\"setup_future_usage\":\"off_session\",\"mandate_data\":{\"update_mandate_id\":\"{{mandate_id}}\",\"customer_acceptance\":{\"acceptance_type\":\"offline\",\"accepted_at\":\"1963-05-03T04:07:52.723Z\",\"online\":{\"ip_address\":\"127.0.0.1\",\"user_agent\":\"amet irure esse\"}}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"count_tickets\":1,\"transaction_number\":\"5590045\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "List - Mandates-copy", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "", - "// Response body should have value \"active\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'active'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"active\");", - " },", - " );", - "}", - "", - "pm.test(\"[POST]::/payments - Verify last 4 digits of the card\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.last4_digits).to.eql(\"1111\");", - "});", - "", - "pm.test(\"[POST]::/payments - Verify card expiration month\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.card_exp_month).to.eql(\"12\");", - "});", - "", - "pm.test(\"[POST]::/payments - Verify card expiration year\", function () {", - " var jsonData = pm.response.json();", - " pm.expect(jsonData.card.card_exp_year).to.eql(\"30\");", - "});", - "", - "", - "", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/mandates/:id", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "mandates", - ":id" - ], - "variable": [ - { - "key": "id", - "value": "{{mandate_id}}", - "description": "(Required) Unique mandate id" - } - ] - }, - "description": "To list the details of a mandate" - }, - "response": [] - }, - { - "name": "Recurring Payments - Create", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[POST]::/payments - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[POST]::/payments - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set mandate_id as variable for jsonData.mandate_id", - "if (jsonData?.mandate_id) {", - " pm.collectionVariables.set(\"mandate_id\", jsonData.mandate_id);", - " console.log(", - " \"- use {{mandate_id}} as collection variable for value\",", - " jsonData.mandate_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{mandate_id}}, as jsonData.mandate_id is undefined.\",", - " );", - "}", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"payment_method_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'payment_method_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.payment_method_data !== \"undefined\").to.be.true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"{{customer_id}}\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"mandate_id\":\"{{mandate_id}}\",\"off_session\":true,\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"likhin\",\"last_name\":\"bopanna\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\"}" - }, - "url": { - "raw": "{{baseUrl}}/payments", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments" - ] - }, - "description": "To process a payment you will have to create a payment, attach a payment method and confirm. Depending on the user journey you wish to achieve, you may opt to all the steps in a single request or in a sequence of API request using following APIs: (i) Payments - Update, (ii) Payments - Confirm, and (iii) Payments - Capture" - }, - "response": [] - }, - { - "name": "Payments - Retrieve-copy", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[GET]::/payments/:id - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(\"[GET]::/payments/:id - Content-Type is application/json\", function () {", - " pm.expect(pm.response.headers.get(\"Content-Type\")).to.include(", - " \"application/json\",", - " );", - "});", - "", - "// Validate if response has JSON Body", - "pm.test(\"[GET]::/payments/:id - Response has JSON Body\", function () {", - " pm.response.to.have.jsonBody();", - "});", - "", - "// Set response object as internal variable", - "let jsonData = {};", - "try {", - " jsonData = pm.response.json();", - "} catch (e) {}", - "", - "// pm.collectionVariables - Set payment_id as variable for jsonData.payment_id", - "if (jsonData?.payment_id) {", - " pm.collectionVariables.set(\"payment_id\", jsonData.payment_id);", - " console.log(", - " \"- use {{payment_id}} as collection variable for value\",", - " jsonData.payment_id,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{payment_id}}, as jsonData.payment_id is undefined.\",", - " );", - "}", - "", - "", - "// pm.collectionVariables - Set client_secret as variable for jsonData.client_secret", - "if (jsonData?.client_secret) {", - " pm.collectionVariables.set(\"client_secret\", jsonData.client_secret);", - " console.log(", - " \"- use {{client_secret}} as collection variable for value\",", - " jsonData.client_secret,", - " );", - "} else {", - " console.log(", - " \"INFO - Unable to assign variable {{client_secret}}, as jsonData.client_secret is undefined.\",", - " );", - "}", - "", - "// Response body should have value \"Succeeded\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id - Content check if value for 'status' matches 'succeeded'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"succeeded\");", - " },", - " );", - "}", - "", - "// Response body should have \"mandate_id\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_id' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_id !== \"undefined\").to.be.true;", - " },", - ");", - "", - "// Response body should have \"mandate_data\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'mandate_data' exists\",", - " function () {", - " pm.expect(typeof jsonData.mandate_data !== \"undefined\").to.be.true;", - " },", - ");", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "method": "GET", - "header": [ - { - "key": "Accept", - "value": "application/json" - } - ], - "url": { - "raw": "{{baseUrl}}/payments/:id?force_sync=true", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id" - ], - "query": [ - { - "key": "force_sync", - "value": "true" - } - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "To retrieve the properties of a Payment. This may be used to get the status of a previously initiated payment or next action for an ongoing payment" - }, - "response": [] - } - ] - }, - { - "name": "Scenario19-Create 3ds payment", + "name": "Scenario18-Create 3ds payment", "item": [ { "name": "Payments - Create", @@ -13025,7 +12161,7 @@ ] }, { - "name": "Scenario20-Create 3ds mandate", + "name": "Scenario19-Create 3ds mandate", "item": [ { "name": "Payments - Create", @@ -13259,7 +12395,7 @@ ] }, { - "name": "Scenario21-Create a mandate without customer acceptance", + "name": "Scenario20-Create a mandate without customer acceptance", "item": [ { "name": "Payments - Create",