From ecab2b1f512eb7e78ca2e75c20b3adc753b97a2f Mon Sep 17 00:00:00 2001 From: Jeeva Ramachandran <120017870+JeevaRamu0104@users.noreply.github.com> Date: Tue, 28 Jan 2025 13:16:56 +0530 Subject: [PATCH 001/133] chore: fix `toml` format to address wasm build failure (#6967) --- crates/connector_configs/toml/production.toml | 1 + crates/connector_configs/toml/sandbox.toml | 11 ++++++----- docker/wasm-build.Dockerfile | 5 ++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 35f2f14c748..5b963e07801 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1478,6 +1478,7 @@ key2="Certificate Key" payment_method_type = "Visa" [jpmorgan.connector_auth.BodyKey] api_key="Access Token" +key1="Client Secret" [klarna] [[klarna.pay_later]] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index ed903812219..50c13dc8596 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1714,6 +1714,7 @@ key2="Certificate Key" payment_method_type = "Visa" [jpmorgan.connector_auth.BodyKey] api_key="Access Token" +key1="Client Secret" [klarna] [[klarna.pay_later]] @@ -4374,7 +4375,7 @@ placeholder="Enter locale" required=true type="Text" -[[ctp_mastercard.metadata.card_brands]] +[ctp_mastercard.metadata.card_brands] name="card_brands" label="Card Brands" placeholder="Enter Card Brands" @@ -4382,28 +4383,28 @@ required=true type="MultiSelect" options=["visa","mastercard"] -[[ctp_mastercard.metadata.acquirer_bin]] +[ctp_mastercard.metadata.acquirer_bin] name="acquirer_bin" label="Acquire Bin" placeholder="Enter Acquirer Bin" required=true type="Text" -[[ctp_mastercard.metadata.acquirer_merchant_id]] +[ctp_mastercard.metadata.acquirer_merchant_id] name="acquirer_merchant_id" label="Acquire Merchant Id" placeholder="Enter Acquirer Merchant Id" required=true type="Text" -[[ctp_mastercard.metadata.merchant_category_code]] +[ctp_mastercard.metadata.merchant_category_code] name="merchant_category_code" label="Merchant Category Code" placeholder="Enter Merchant Category Code" required=true type="Text" -[[ctp_mastercard.metadata.merchant_country_code]] +[ctp_mastercard.metadata.merchant_country_code] name="merchant_country_code" label="Merchant Country Code" placeholder="Enter Merchant Country Code" diff --git a/docker/wasm-build.Dockerfile b/docker/wasm-build.Dockerfile index 015a055e4fd..faa2d211bd8 100644 --- a/docker/wasm-build.Dockerfile +++ b/docker/wasm-build.Dockerfile @@ -1,7 +1,6 @@ FROM rust:latest as builder -ARG RUN_ENV=sandbox -ARG EXTRA_FEATURES="" +ARG FEATURES="" ARG VERSION_FEATURE_SET="v1" RUN apt-get update \ @@ -19,7 +18,7 @@ ENV env=$env COPY . . RUN echo env RUN cargo install wasm-pack -RUN wasm-pack build --target web --out-dir /tmp/wasm --out-name euclid crates/euclid_wasm -- --features ${VERSION_FEATURE_SET},${RUN_ENV},${EXTRA_FEATURES} +RUN wasm-pack build --target web --out-dir /tmp/wasm --out-name euclid crates/euclid_wasm -- --features ${VERSION_FEATURE_SET},${FEATURES} FROM scratch From 858866f9f361c16b76ed79b42814b648f2050f08 Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Tue, 28 Jan 2025 23:27:23 +0530 Subject: [PATCH 002/133] refactor(currency_conversion): re frame the currency_conversion crate to make api calls on background thread (#6906) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 11 +- config/deployments/env_specific.toml | 11 +- config/development.toml | 9 +- config/docker_compose.toml | 9 +- crates/analytics/docs/README.md | 6 +- crates/router/src/configs/settings.rs | 9 +- crates/router/src/core/currency.rs | 26 +-- crates/router/src/utils/currency.rs | 255 +++++++++++--------------- loadtest/config/development.toml | 9 +- 9 files changed, 142 insertions(+), 203 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index c860420da38..ecc2d2127ce 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -74,13 +74,10 @@ max_feed_count = 200 # The maximum number of frames that will be fe # This section provides configs for currency conversion api [forex_api] -call_delay = 21600 # Api calls are made after every 6 hrs -local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 -local_fetch_retry_delay = 1000 # Retry delay for checking write condition -api_timeout = 20000 # Api timeouts once it crosses 20000 ms -api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api -fallback_api_key = "YOUR API KEY" # Api key for the fallback service -redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called +call_delay = 21600 # Expiration time for data in cache as well as redis in seconds +api_key = "" # Api key for making request to foreign exchange Api +fallback_api_key = "" # Api key for the fallback service +redis_lock_timeout = 100 # Redis remains write locked for 100 s once the acquire_redis_lock is called # Logging configuration. Logging can be either to file or console or both. diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 84550922d4a..8c846ff422e 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -101,13 +101,10 @@ bucket_name = "bucket" # The AWS S3 bucket name for file storage # This section provides configs for currency conversion api [forex_api] -call_delay = 21600 # Api calls are made after every 6 hrs -local_fetch_retry_count = 5 # Fetch from Local cache has retry count as 5 -local_fetch_retry_delay = 1000 # Retry delay for checking write condition -api_timeout = 20000 # Api timeouts once it crosses 20000 ms -api_key = "YOUR API KEY HERE" # Api key for making request to foreign exchange Api -fallback_api_key = "YOUR API KEY" # Api key for the fallback service -redis_lock_timeout = 26000 # Redis remains write locked for 26000 ms once the acquire_redis_lock is called +call_delay = 21600 # Expiration time for data in cache as well as redis in seconds +api_key = "" # Api key for making request to foreign exchange Api +fallback_api_key = "" # Api key for the fallback service +redis_lock_timeout = 100 # Redis remains write locked for 100 s once the acquire_redis_lock is called [jwekey] # 3 priv/pub key pair vault_encryption_key = "" # public key in pem format, corresponding private key in rust locker diff --git a/config/development.toml b/config/development.toml index a32102aeebf..6bce5cb89d7 100644 --- a/config/development.toml +++ b/config/development.toml @@ -78,12 +78,9 @@ ttl_for_storage_in_secs = 220752000 [forex_api] call_delay = 21600 -local_fetch_retry_count = 5 -local_fetch_retry_delay = 1000 -api_timeout = 20000 -api_key = "YOUR API KEY HERE" -fallback_api_key = "YOUR API KEY HERE" -redis_lock_timeout = 26000 +api_key = "" +fallback_api_key = "" +redis_lock_timeout = 100 [jwekey] vault_encryption_key = "" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 4296a5931dc..293e9a313e9 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -31,12 +31,9 @@ pool_size = 5 [forex_api] call_delay = 21600 -local_fetch_retry_count = 5 -local_fetch_retry_delay = 1000 -api_timeout = 20000 -api_key = "YOUR API KEY HERE" -fallback_api_key = "YOUR API KEY HERE" -redis_lock_timeout = 26000 +api_key = "" +fallback_api_key = "" +redis_lock_timeout = 100 [replica_database] username = "db_user" diff --git a/crates/analytics/docs/README.md b/crates/analytics/docs/README.md index e24dc6c5af7..71190613a10 100644 --- a/crates/analytics/docs/README.md +++ b/crates/analytics/docs/README.md @@ -115,8 +115,8 @@ To configure the Forex APIs, update the `config/development.toml` or `config/doc ```toml [forex_api] -api_key = "YOUR API KEY HERE" # Replace the placeholder with your Primary API Key -fallback_api_key = "YOUR API KEY HERE" # Replace the placeholder with your Fallback API Key +api_key = "" +fallback_api_key = "" ``` ### Important Note ```bash @@ -159,4 +159,4 @@ To view the data on the OpenSearch dashboard perform the following steps: - Select a time field that will be used for time-based queries - Save the index pattern -Now, head on to `Discover` under the `OpenSearch Dashboards` tab, to select the newly created index pattern and query the data \ No newline at end of file +Now, head on to `Discover` under the `OpenSearch Dashboards` tab, to select the newly created index pattern and query the data diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 577b29b0534..fa3d004aada 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -303,16 +303,11 @@ pub struct PaymentLink { #[derive(Debug, Deserialize, Clone, Default)] #[serde(default)] pub struct ForexApi { - pub local_fetch_retry_count: u64, pub api_key: Secret, pub fallback_api_key: Secret, - /// in ms + /// in s pub call_delay: i64, - /// in ms - pub local_fetch_retry_delay: u64, - /// in ms - pub api_timeout: u64, - /// in ms + /// in s pub redis_lock_timeout: u64, } diff --git a/crates/router/src/core/currency.rs b/crates/router/src/core/currency.rs index 912484b014a..8c1a8512892 100644 --- a/crates/router/src/core/currency.rs +++ b/crates/router/src/core/currency.rs @@ -15,16 +15,11 @@ pub async fn retrieve_forex( ) -> CustomResult, ApiErrorResponse> { let forex_api = state.conf.forex_api.get_inner(); Ok(ApplicationResponse::Json( - get_forex_rates( - &state, - forex_api.call_delay, - forex_api.local_fetch_retry_delay, - forex_api.local_fetch_retry_count, - ) - .await - .change_context(ApiErrorResponse::GenericNotFoundError { - message: "Unable to fetch forex rates".to_string(), - })?, + get_forex_rates(&state, forex_api.call_delay) + .await + .change_context(ApiErrorResponse::GenericNotFoundError { + message: "Unable to fetch forex rates".to_string(), + })?, )) } @@ -53,14 +48,9 @@ pub async fn get_forex_exchange_rates( state: SessionState, ) -> CustomResult { let forex_api = state.conf.forex_api.get_inner(); - let rates = get_forex_rates( - &state, - forex_api.call_delay, - forex_api.local_fetch_retry_delay, - forex_api.local_fetch_retry_count, - ) - .await - .change_context(AnalyticsError::ForexFetchFailed)?; + let rates = get_forex_rates(&state, forex_api.call_delay) + .await + .change_context(AnalyticsError::ForexFetchFailed)?; Ok((*rates.data).clone()) } diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs index 9ab2780da73..c10ebf3dac1 100644 --- a/crates/router/src/utils/currency.rs +++ b/crates/router/src/utils/currency.rs @@ -1,4 +1,4 @@ -use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc, time::Duration}; +use std::{collections::HashMap, ops::Deref, str::FromStr, sync::Arc}; use api_models::enums; use common_utils::{date_time, errors::CustomResult, events::ApiEventMetric, ext_traits::AsyncExt}; @@ -10,7 +10,8 @@ use redis_interface::DelReply; use router_env::{instrument, tracing}; use rust_decimal::Decimal; use strum::IntoEnumIterator; -use tokio::{sync::RwLock, time::sleep}; +use tokio::sync::RwLock; +use tracing_futures::Instrument; use crate::{ logger, @@ -50,10 +51,14 @@ pub enum ForexCacheError { CouldNotAcquireLock, #[error("Provided currency not acceptable")] CurrencyNotAcceptable, + #[error("Forex configuration error: {0}")] + ConfigurationError(String), #[error("Incorrect entries in default Currency response")] DefaultCurrencyParsingError, #[error("Entry not found in cache")] EntryNotFound, + #[error("Forex data unavailable")] + ForexDataUnavailable, #[error("Expiration time invalid")] InvalidLogExpiry, #[error("Error reading local")] @@ -107,44 +112,19 @@ impl FxExchangeRatesCacheEntry { } } -async fn retrieve_forex_from_local() -> Option { +async fn retrieve_forex_from_local_cache() -> Option { FX_EXCHANGE_RATES_CACHE.read().await.clone() } -async fn save_forex_to_local( +async fn save_forex_data_to_local_cache( exchange_rates_cache_entry: FxExchangeRatesCacheEntry, ) -> CustomResult<(), ForexCacheError> { let mut local = FX_EXCHANGE_RATES_CACHE.write().await; *local = Some(exchange_rates_cache_entry); + logger::debug!("forex_log: forex saved in cache"); Ok(()) } -// Alternative handler for handling the case, When no data in local as well as redis -#[allow(dead_code)] -async fn waited_fetch_and_update_caches( - state: &SessionState, - local_fetch_retry_delay: u64, - local_fetch_retry_count: u64, -) -> CustomResult { - for _n in 1..local_fetch_retry_count { - sleep(Duration::from_millis(local_fetch_retry_delay)).await; - //read from redis and update local plus break the loop and return - match retrieve_forex_from_redis(state).await { - Ok(Some(rates)) => { - save_forex_to_local(rates.clone()).await?; - return Ok(rates.clone()); - } - Ok(None) => continue, - Err(error) => { - logger::error!(?error); - continue; - } - } - } - //acquire lock one last time and try to fetch and update local & redis - successive_fetch_and_save_forex(state, None).await -} - impl TryFrom for ExchangeRates { type Error = error_stack::Report; fn try_from(value: DefaultExchangeRates) -> Result { @@ -178,102 +158,108 @@ impl From for CurrencyFactors { pub async fn get_forex_rates( state: &SessionState, call_delay: i64, - local_fetch_retry_delay: u64, - local_fetch_retry_count: u64, ) -> CustomResult { - if let Some(local_rates) = retrieve_forex_from_local().await { + if let Some(local_rates) = retrieve_forex_from_local_cache().await { if local_rates.is_expired(call_delay) { // expired local data - handler_local_expired(state, call_delay, local_rates).await + logger::debug!("forex_log: Forex stored in cache is expired"); + call_forex_api_and_save_data_to_cache_and_redis(state, Some(local_rates)).await } else { // Valid data present in local + logger::debug!("forex_log: forex found in cache"); Ok(local_rates) } } else { // No data in local - handler_local_no_data( - state, - call_delay, - local_fetch_retry_delay, - local_fetch_retry_count, - ) - .await + call_api_if_redis_forex_data_expired(state, call_delay).await } } -async fn handler_local_no_data( +async fn call_api_if_redis_forex_data_expired( state: &SessionState, call_delay: i64, - _local_fetch_retry_delay: u64, - _local_fetch_retry_count: u64, ) -> CustomResult { - match retrieve_forex_from_redis(state).await { - Ok(Some(data)) => fallback_forex_redis_check(state, data, call_delay).await, + match retrieve_forex_data_from_redis(state).await { + Ok(Some(data)) => call_forex_api_if_redis_data_expired(state, data, call_delay).await, Ok(None) => { // No data in local as well as redis - Ok(successive_fetch_and_save_forex(state, None).await?) + call_forex_api_and_save_data_to_cache_and_redis(state, None).await?; + Err(ForexCacheError::ForexDataUnavailable.into()) } Err(error) => { - logger::error!(?error); - Ok(successive_fetch_and_save_forex(state, None).await?) + // Error in deriving forex rates from redis + logger::error!("forex_error: {:?}", error); + call_forex_api_and_save_data_to_cache_and_redis(state, None).await?; + Err(ForexCacheError::ForexDataUnavailable.into()) } } } -async fn successive_fetch_and_save_forex( +async fn call_forex_api_and_save_data_to_cache_and_redis( state: &SessionState, stale_redis_data: Option, ) -> CustomResult { - match acquire_redis_lock(state).await { - Ok(lock_acquired) => { - if !lock_acquired { - return stale_redis_data.ok_or(ForexCacheError::CouldNotAcquireLock.into()); + // spawn a new thread and do the api fetch and write operations on redis. + let forex_api_key = state.conf.forex_api.get_inner().api_key.peek(); + if forex_api_key.is_empty() { + Err(ForexCacheError::ConfigurationError("api_keys not provided".into()).into()) + } else { + let state = state.clone(); + tokio::spawn( + async move { + acquire_redis_lock_and_call_forex_api(&state) + .await + .map_err(|err| { + logger::error!(forex_error=?err); + }) + .ok(); } - let api_rates = fetch_forex_rates(state).await; - match api_rates { - Ok(rates) => successive_save_data_to_redis_local(state, rates).await, - Err(error) => { - // API not able to fetch data call secondary service - logger::error!(?error); - let secondary_api_rates = fallback_fetch_forex_rates(state).await; - match secondary_api_rates { - Ok(rates) => Ok(successive_save_data_to_redis_local(state, rates).await?), - Err(error) => stale_redis_data.ok_or({ - logger::error!(?error); - release_redis_lock(state).await?; - ForexCacheError::ApiUnresponsive.into() - }), + .in_current_span(), + ); + stale_redis_data.ok_or(ForexCacheError::EntryNotFound.into()) + } +} + +async fn acquire_redis_lock_and_call_forex_api( + state: &SessionState, +) -> CustomResult<(), ForexCacheError> { + let lock_acquired = acquire_redis_lock(state).await?; + if !lock_acquired { + Err(ForexCacheError::CouldNotAcquireLock.into()) + } else { + logger::debug!("forex_log: redis lock acquired"); + let api_rates = fetch_forex_rates_from_primary_api(state).await; + match api_rates { + Ok(rates) => save_forex_data_to_cache_and_redis(state, rates).await, + Err(error) => { + logger::error!(forex_error=?error,"primary_forex_error"); + // API not able to fetch data call secondary service + let secondary_api_rates = fetch_forex_rates_from_fallback_api(state).await; + match secondary_api_rates { + Ok(rates) => save_forex_data_to_cache_and_redis(state, rates).await, + Err(error) => { + release_redis_lock(state).await?; + Err(error) } } } } - Err(error) => stale_redis_data.ok_or({ - logger::error!(?error); - ForexCacheError::ApiUnresponsive.into() - }), } } -async fn successive_save_data_to_redis_local( +async fn save_forex_data_to_cache_and_redis( state: &SessionState, forex: FxExchangeRatesCacheEntry, -) -> CustomResult { - Ok(save_forex_to_redis(state, &forex) +) -> CustomResult<(), ForexCacheError> { + save_forex_data_to_redis(state, &forex) .await .async_and_then(|_rates| release_redis_lock(state)) .await - .async_and_then(|_val| save_forex_to_local(forex.clone())) + .async_and_then(|_val| save_forex_data_to_local_cache(forex.clone())) .await - .map_or_else( - |error| { - logger::error!(?error); - forex.clone() - }, - |_| forex.clone(), - )) } -async fn fallback_forex_redis_check( +async fn call_forex_api_if_redis_data_expired( state: &SessionState, redis_data: FxExchangeRatesCacheEntry, call_delay: i64, @@ -282,57 +268,30 @@ async fn fallback_forex_redis_check( Some(redis_forex) => { // Valid data present in redis let exchange_rates = FxExchangeRatesCacheEntry::new(redis_forex.as_ref().clone()); - save_forex_to_local(exchange_rates.clone()).await?; + logger::debug!("forex_log: forex response found in redis"); + save_forex_data_to_local_cache(exchange_rates.clone()).await?; Ok(exchange_rates) } None => { // redis expired - successive_fetch_and_save_forex(state, Some(redis_data)).await - } - } -} - -async fn handler_local_expired( - state: &SessionState, - call_delay: i64, - local_rates: FxExchangeRatesCacheEntry, -) -> CustomResult { - match retrieve_forex_from_redis(state).await { - Ok(redis_data) => { - match is_redis_expired(redis_data.as_ref(), call_delay).await { - Some(redis_forex) => { - // Valid data present in redis - let exchange_rates = - FxExchangeRatesCacheEntry::new(redis_forex.as_ref().clone()); - save_forex_to_local(exchange_rates.clone()).await?; - Ok(exchange_rates) - } - None => { - // Redis is expired going for API request - successive_fetch_and_save_forex(state, Some(local_rates)).await - } - } - } - Err(error) => { - // data not present in redis waited fetch - logger::error!(?error); - successive_fetch_and_save_forex(state, Some(local_rates)).await + call_forex_api_and_save_data_to_cache_and_redis(state, Some(redis_data)).await } } } -async fn fetch_forex_rates( +async fn fetch_forex_rates_from_primary_api( state: &SessionState, ) -> Result> { let forex_api_key = state.conf.forex_api.get_inner().api_key.peek(); + logger::debug!("forex_log: Primary api call for forex fetch"); let forex_url: String = format!("{}{}{}", FOREX_BASE_URL, forex_api_key, FOREX_BASE_CURRENCY); let forex_request = services::RequestBuilder::new() .method(services::Method::Get) .url(&forex_url) .build(); - logger::info!(?forex_request); + logger::info!(primary_forex_request=?forex_request,"forex_log: Primary api call for forex fetch"); let response = state .api_client .send_request( @@ -352,7 +311,7 @@ async fn fetch_forex_rates( "Unable to parse response received from primary api into ForexResponse", )?; - logger::info!("{:?}", forex_response); + logger::info!(primary_forex_response=?forex_response,"forex_log"); let mut conversions: HashMap = HashMap::new(); for enum_curr in enums::Currency::iter() { @@ -361,7 +320,10 @@ async fn fetch_forex_rates( let from_factor = match Decimal::new(1, 0).checked_div(**rate) { Some(rate) => rate, None => { - logger::error!("Rates for {} not received from API", &enum_curr); + logger::error!( + "forex_error: Rates for {} not received from API", + &enum_curr + ); continue; } }; @@ -369,7 +331,10 @@ async fn fetch_forex_rates( conversions.insert(enum_curr, currency_factors); } None => { - logger::error!("Rates for {} not received from API", &enum_curr); + logger::error!( + "forex_error: Rates for {} not received from API", + &enum_curr + ); } }; } @@ -380,7 +345,7 @@ async fn fetch_forex_rates( ))) } -pub async fn fallback_fetch_forex_rates( +pub async fn fetch_forex_rates_from_fallback_api( state: &SessionState, ) -> CustomResult { let fallback_forex_api_key = state.conf.forex_api.get_inner().fallback_api_key.peek(); @@ -392,7 +357,7 @@ pub async fn fallback_fetch_forex_rates( .url(&fallback_forex_url) .build(); - logger::info!(?fallback_forex_request); + logger::info!(fallback_forex_request=?fallback_forex_request,"forex_log: Fallback api call for forex fetch"); let response = state .api_client .send_request( @@ -413,7 +378,8 @@ pub async fn fallback_fetch_forex_rates( "Unable to parse response received from falback api into ForexResponse", )?; - logger::info!("{:?}", fallback_forex_response); + logger::info!(fallback_forex_response=?fallback_forex_response,"forex_log"); + let mut conversions: HashMap = HashMap::new(); for enum_curr in enums::Currency::iter() { match fallback_forex_response.quotes.get( @@ -428,7 +394,10 @@ pub async fn fallback_fetch_forex_rates( let from_factor = match Decimal::new(1, 0).checked_div(**rate) { Some(rate) => rate, None => { - logger::error!("Rates for {} not received from API", &enum_curr); + logger::error!( + "forex_error: Rates for {} not received from API", + &enum_curr + ); continue; } }; @@ -441,7 +410,10 @@ pub async fn fallback_fetch_forex_rates( CurrencyFactors::new(Decimal::new(1, 0), Decimal::new(1, 0)); conversions.insert(enum_curr, currency_factors); } else { - logger::error!("Rates for {} not received from API", &enum_curr); + logger::error!( + "forex_error: Rates for {} not received from API", + &enum_curr + ); } } }; @@ -450,17 +422,18 @@ pub async fn fallback_fetch_forex_rates( let rates = FxExchangeRatesCacheEntry::new(ExchangeRates::new(enums::Currency::USD, conversions)); match acquire_redis_lock(state).await { - Ok(_) => Ok(successive_save_data_to_redis_local(state, rates).await?), - Err(e) => { - logger::error!(?e); + Ok(_) => { + save_forex_data_to_cache_and_redis(state, rates.clone()).await?; Ok(rates) } + Err(e) => Err(e), } } async fn release_redis_lock( state: &SessionState, ) -> Result> { + logger::debug!("forex_log: Releasing redis lock"); state .store .get_redis_conn() @@ -473,6 +446,7 @@ async fn release_redis_lock( async fn acquire_redis_lock(state: &SessionState) -> CustomResult { let forex_api = state.conf.forex_api.get_inner(); + logger::debug!("forex_log: Acquiring redis lock"); state .store .get_redis_conn() @@ -481,11 +455,8 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult CustomResult CustomResult<(), ForexCacheError> { + logger::debug!("forex_log: Saving forex to redis"); app_state .store .get_redis_conn() @@ -508,9 +480,10 @@ async fn save_forex_to_redis( .attach_printable("Unable to save forex data to redis") } -async fn retrieve_forex_from_redis( +async fn retrieve_forex_data_from_redis( app_state: &SessionState, ) -> CustomResult, ForexCacheError> { + logger::debug!("forex_log: Retrieving forex from redis"); app_state .store .get_redis_conn() @@ -529,6 +502,7 @@ async fn is_redis_expired( if cache.timestamp + call_delay > date_time::now_unix_timestamp() { Some(cache.data.clone()) } else { + logger::debug!("forex_log: Forex stored in redis is expired"); None } }) @@ -542,14 +516,9 @@ pub async fn convert_currency( from_currency: String, ) -> CustomResult { let forex_api = state.conf.forex_api.get_inner(); - let rates = get_forex_rates( - &state, - forex_api.call_delay, - forex_api.local_fetch_retry_delay, - forex_api.local_fetch_retry_count, - ) - .await - .change_context(ForexCacheError::ApiError)?; + let rates = get_forex_rates(&state, forex_api.call_delay) + .await + .change_context(ForexCacheError::ApiError)?; let to_currency = enums::Currency::from_str(to_currency.as_str()) .change_context(ForexCacheError::CurrencyNotAcceptable) diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index e90eb16ab61..61f5debb4d0 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -48,12 +48,9 @@ ttl_for_storage_in_secs = 220752000 [forex_api] call_delay = 21600 -local_fetch_retry_count = 5 -local_fetch_retry_delay = 1000 -api_timeout = 20000 -api_key = "YOUR API KEY HERE" -fallback_api_key = "YOUR API KEY HERE" -redis_lock_timeout = 26000 +api_key = "" +fallback_api_key = "" +redis_lock_timeout = 100 [eph_key] validity = 1 From 5707297621538ccf47f7314ca564783d6f289317 Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Wed, 29 Jan 2025 00:38:17 +0530 Subject: [PATCH 003/133] fix(multitenancy): add a fallback for get commands in redis (#7043) --- crates/drainer/src/health_check.rs | 10 +- crates/drainer/src/stream.rs | 8 +- crates/redis_interface/Cargo.toml | 3 + crates/redis_interface/src/commands.rs | 283 ++++++++++++------ crates/redis_interface/src/types.rs | 23 +- crates/router/src/core/api_locking.rs | 9 +- crates/router/src/core/health_check.rs | 6 +- .../router/src/core/payment_methods/cards.rs | 4 +- .../router/src/core/payment_methods/vault.rs | 8 +- crates/router/src/core/payments.rs | 6 +- crates/router/src/core/payments/helpers.rs | 8 +- .../payments/operations/payment_confirm.rs | 2 +- crates/router/src/core/payments/types.rs | 12 +- crates/router/src/core/payouts/helpers.rs | 2 +- crates/router/src/core/pm_auth.rs | 8 +- crates/router/src/core/poll.rs | 2 +- crates/router/src/core/webhooks/incoming.rs | 2 +- crates/router/src/db/ephemeral_key.rs | 29 +- .../src/db/merchant_connector_account.rs | 4 +- .../router/src/routes/dummy_connector/core.rs | 4 +- .../src/routes/dummy_connector/utils.rs | 8 +- crates/router/src/routes/payment_methods.rs | 11 +- .../src/services/authentication/blacklist.rs | 14 +- crates/router/src/services/authorization.rs | 8 +- crates/router/src/services/openidconnect.rs | 4 +- crates/router/src/utils/currency.rs | 8 +- crates/router/src/utils/user.rs | 4 +- .../router/src/utils/user/two_factor_auth.rs | 30 +- crates/router/tests/cache.rs | 2 +- crates/scheduler/src/db/queue.rs | 14 +- crates/scheduler/src/utils.rs | 4 +- crates/storage_impl/src/lib.rs | 4 +- crates/storage_impl/src/redis/cache.rs | 8 +- crates/storage_impl/src/redis/kv_store.rs | 14 +- 34 files changed, 362 insertions(+), 204 deletions(-) diff --git a/crates/drainer/src/health_check.rs b/crates/drainer/src/health_check.rs index 2ca2c1cc79c..aaf455663f5 100644 --- a/crates/drainer/src/health_check.rs +++ b/crates/drainer/src/health_check.rs @@ -165,21 +165,21 @@ impl HealthCheckInterface for Store { let redis_conn = self.redis_conn.clone(); redis_conn - .serialize_and_set_key_with_expiry("test_key", "test_value", 30) + .serialize_and_set_key_with_expiry(&"test_key".into(), "test_value", 30) .await .change_context(HealthCheckRedisError::SetFailed)?; logger::debug!("Redis set_key was successful"); redis_conn - .get_key::<()>("test_key") + .get_key::<()>(&"test_key".into()) .await .change_context(HealthCheckRedisError::GetFailed)?; logger::debug!("Redis get_key was successful"); redis_conn - .delete_key("test_key") + .delete_key(&"test_key".into()) .await .change_context(HealthCheckRedisError::DeleteFailed)?; @@ -187,7 +187,7 @@ impl HealthCheckInterface for Store { redis_conn .stream_append_entry( - TEST_STREAM_NAME, + &TEST_STREAM_NAME.into(), &redis_interface::RedisEntryId::AutoGeneratedID, TEST_STREAM_DATA.to_vec(), ) @@ -216,7 +216,7 @@ impl HealthCheckInterface for Store { redis_conn .stream_trim_entries( - TEST_STREAM_NAME, + &TEST_STREAM_NAME.into(), ( redis_interface::StreamCapKind::MinID, redis_interface::StreamCapTrim::Exact, diff --git a/crates/drainer/src/stream.rs b/crates/drainer/src/stream.rs index f5b41c53672..9e092b038e8 100644 --- a/crates/drainer/src/stream.rs +++ b/crates/drainer/src/stream.rs @@ -31,7 +31,7 @@ impl Store { match self .redis_conn - .set_key_if_not_exists_with_expiry(stream_key_flag.as_str(), true, None) + .set_key_if_not_exists_with_expiry(&stream_key_flag.as_str().into(), true, None) .await { Ok(resp) => resp == redis::types::SetnxReply::KeySet, @@ -43,7 +43,7 @@ impl Store { } pub async fn make_stream_available(&self, stream_name_flag: &str) -> errors::DrainerResult<()> { - match self.redis_conn.delete_key(stream_name_flag).await { + match self.redis_conn.delete_key(&stream_name_flag.into()).await { Ok(redis::DelReply::KeyDeleted) => Ok(()), Ok(redis::DelReply::KeyNotDeleted) => { logger::error!("Tried to unlock a stream which is already unlocked"); @@ -87,14 +87,14 @@ impl Store { common_utils::date_time::time_it::, _, _>(|| async { let trim_result = self .redis_conn - .stream_trim_entries(stream_name, (trim_kind, trim_type, trim_id)) + .stream_trim_entries(&stream_name.into(), (trim_kind, trim_type, trim_id)) .await .map_err(errors::DrainerError::from)?; // Since xtrim deletes entries below given id excluding the given id. // Hence, deleting the minimum entry id self.redis_conn - .stream_delete_entries(stream_name, minimum_entry_id) + .stream_delete_entries(&stream_name.into(), minimum_entry_id) .await .map_err(errors::DrainerError::from)?; diff --git a/crates/redis_interface/Cargo.toml b/crates/redis_interface/Cargo.toml index f9159564830..87f5721365c 100644 --- a/crates/redis_interface/Cargo.toml +++ b/crates/redis_interface/Cargo.toml @@ -7,6 +7,9 @@ rust-version.workspace = true readme = "README.md" license.workspace = true +[features] +multitenancy_fallback = [] + [dependencies] error-stack = "0.4.1" fred = { version = "7.1.2", features = ["metrics", "partial-tracing", "subscriber-client", "check-unresponsive"] } diff --git a/crates/redis_interface/src/commands.rs b/crates/redis_interface/src/commands.rs index 56e9ab98104..affd2b0faa9 100644 --- a/crates/redis_interface/src/commands.rs +++ b/crates/redis_interface/src/commands.rs @@ -17,8 +17,7 @@ use fred::{ prelude::{LuaInterface, RedisErrorKind}, types::{ Expiration, FromRedis, MultipleIDs, MultipleKeys, MultipleOrderedPairs, MultipleStrings, - MultipleValues, RedisKey, RedisMap, RedisValue, ScanType, Scanner, SetOptions, XCap, - XReadResponse, + MultipleValues, RedisMap, RedisValue, ScanType, Scanner, SetOptions, XCap, XReadResponse, }, }; use futures::StreamExt; @@ -26,7 +25,7 @@ use tracing::instrument; use crate::{ errors, - types::{DelReply, HsetnxReply, MsetnxReply, RedisEntryId, SaddReply, SetnxReply}, + types::{DelReply, HsetnxReply, MsetnxReply, RedisEntryId, RedisKey, SaddReply, SetnxReply}, }; impl super::RedisConnectionPool { @@ -37,15 +36,16 @@ impl super::RedisConnectionPool { format!("{}:{}", self.key_prefix, key) } } + #[instrument(level = "DEBUG", skip(self))] - pub async fn set_key(&self, key: &str, value: V) -> CustomResult<(), errors::RedisError> + pub async fn set_key(&self, key: &RedisKey, value: V) -> CustomResult<(), errors::RedisError> where V: TryInto + Debug + Send + Sync, V::Error: Into + Send + Sync, { self.pool .set( - self.add_prefix(key), + key.tenant_aware_key(self), value, Some(Expiration::EX(self.config.default_ttl.into())), None, @@ -57,7 +57,7 @@ impl super::RedisConnectionPool { pub async fn set_key_without_modifying_ttl( &self, - key: &str, + key: &RedisKey, value: V, ) -> CustomResult<(), errors::RedisError> where @@ -65,7 +65,13 @@ impl super::RedisConnectionPool { V::Error: Into + Send + Sync, { self.pool - .set(key, value, Some(Expiration::KEEPTTL), None, false) + .set( + key.tenant_aware_key(self), + value, + Some(Expiration::KEEPTTL), + None, + false, + ) .await .change_context(errors::RedisError::SetFailed) } @@ -87,7 +93,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_key_if_not_exist( &self, - key: &str, + key: &RedisKey, value: V, ttl: Option, ) -> CustomResult @@ -104,7 +110,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_key( &self, - key: &str, + key: &RedisKey, value: V, ) -> CustomResult<(), errors::RedisError> where @@ -120,7 +126,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_key_without_modifying_ttl( &self, - key: &str, + key: &RedisKey, value: V, ) -> CustomResult<(), errors::RedisError> where @@ -137,7 +143,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_key_with_expiry( &self, - key: &str, + key: &RedisKey, value: V, seconds: i64, ) -> CustomResult<(), errors::RedisError> @@ -150,7 +156,7 @@ impl super::RedisConnectionPool { self.pool .set( - self.add_prefix(key), + key.tenant_aware_key(self), serialized.as_slice(), Some(Expiration::EX(seconds)), None, @@ -161,31 +167,67 @@ impl super::RedisConnectionPool { } #[instrument(level = "DEBUG", skip(self))] - pub async fn get_key(&self, key: &str) -> CustomResult + pub async fn get_key(&self, key: &RedisKey) -> CustomResult where V: FromRedis + Unpin + Send + 'static, { - self.pool - .get(self.add_prefix(key)) + match self + .pool + .get(key.tenant_aware_key(self)) .await .change_context(errors::RedisError::GetFailed) + { + Ok(v) => Ok(v), + Err(_err) => { + #[cfg(not(feature = "multitenancy_fallback"))] + { + Err(_err) + } + + #[cfg(feature = "multitenancy_fallback")] + { + self.pool + .get(key.tenant_unaware_key(self)) + .await + .change_context(errors::RedisError::GetFailed) + } + } + } } #[instrument(level = "DEBUG", skip(self))] - pub async fn exists(&self, key: &str) -> CustomResult + pub async fn exists(&self, key: &RedisKey) -> CustomResult where V: Into + Unpin + Send + 'static, { - self.pool - .exists(self.add_prefix(key)) + match self + .pool + .exists(key.tenant_aware_key(self)) .await .change_context(errors::RedisError::GetFailed) + { + Ok(v) => Ok(v), + Err(_err) => { + #[cfg(not(feature = "multitenancy_fallback"))] + { + Err(_err) + } + + #[cfg(feature = "multitenancy_fallback")] + { + self.pool + .exists(key.tenant_unaware_key(self)) + .await + .change_context(errors::RedisError::GetFailed) + } + } + } } #[instrument(level = "DEBUG", skip(self))] pub async fn get_and_deserialize_key( &self, - key: &str, + key: &RedisKey, type_name: &'static str, ) -> CustomResult where @@ -201,19 +243,37 @@ impl super::RedisConnectionPool { } #[instrument(level = "DEBUG", skip(self))] - pub async fn delete_key(&self, key: &str) -> CustomResult { - self.pool - .del(self.add_prefix(key)) + pub async fn delete_key(&self, key: &RedisKey) -> CustomResult { + match self + .pool + .del(key.tenant_aware_key(self)) .await .change_context(errors::RedisError::DeleteFailed) + { + Ok(v) => Ok(v), + Err(_err) => { + #[cfg(not(feature = "multitenancy_fallback"))] + { + Err(_err) + } + + #[cfg(feature = "multitenancy_fallback")] + { + self.pool + .del(key.tenant_unaware_key(self)) + .await + .change_context(errors::RedisError::DeleteFailed) + } + } + } } #[instrument(level = "DEBUG", skip(self))] pub async fn delete_multiple_keys( &self, - keys: &[String], + keys: &[RedisKey], ) -> CustomResult, errors::RedisError> { - let futures = keys.iter().map(|key| self.pool.del(self.add_prefix(key))); + let futures = keys.iter().map(|key| self.delete_key(key)); let del_result = futures::future::try_join_all(futures) .await @@ -225,7 +285,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_key_with_expiry( &self, - key: &str, + key: &RedisKey, value: V, seconds: i64, ) -> CustomResult<(), errors::RedisError> @@ -235,7 +295,7 @@ impl super::RedisConnectionPool { { self.pool .set( - self.add_prefix(key), + key.tenant_aware_key(self), value, Some(Expiration::EX(seconds)), None, @@ -248,7 +308,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_key_if_not_exists_with_expiry( &self, - key: &str, + key: &RedisKey, value: V, seconds: Option, ) -> CustomResult @@ -258,7 +318,7 @@ impl super::RedisConnectionPool { { self.pool .set( - self.add_prefix(key), + key.tenant_aware_key(self), value, Some(Expiration::EX( seconds.unwrap_or(self.config.default_ttl.into()), @@ -273,11 +333,11 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_expiry( &self, - key: &str, + key: &RedisKey, seconds: i64, ) -> CustomResult<(), errors::RedisError> { self.pool - .expire(self.add_prefix(key), seconds) + .expire(key.tenant_aware_key(self), seconds) .await .change_context(errors::RedisError::SetExpiryFailed) } @@ -285,11 +345,11 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_expire_at( &self, - key: &str, + key: &RedisKey, timestamp: i64, ) -> CustomResult<(), errors::RedisError> { self.pool - .expire_at(self.add_prefix(key), timestamp) + .expire_at(key.tenant_aware_key(self), timestamp) .await .change_context(errors::RedisError::SetExpiryFailed) } @@ -297,7 +357,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_hash_fields( &self, - key: &str, + key: &RedisKey, values: V, ttl: Option, ) -> CustomResult<(), errors::RedisError> @@ -307,7 +367,7 @@ impl super::RedisConnectionPool { { let output: Result<(), _> = self .pool - .hset(self.add_prefix(key), values) + .hset(key.tenant_aware_key(self), values) .await .change_context(errors::RedisError::SetHashFailed); // setting expiry for the key @@ -321,7 +381,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn set_hash_field_if_not_exist( &self, - key: &str, + key: &RedisKey, field: &str, value: V, ttl: Option, @@ -332,7 +392,7 @@ impl super::RedisConnectionPool { { let output: Result = self .pool - .hsetnx(self.add_prefix(key), field, value) + .hsetnx(key.tenant_aware_key(self), field, value) .await .change_context(errors::RedisError::SetHashFieldFailed); @@ -348,7 +408,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_hash_field_if_not_exist( &self, - key: &str, + key: &RedisKey, field: &str, value: V, ttl: Option, @@ -367,7 +427,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn serialize_and_set_multiple_hash_field_if_not_exist( &self, - kv: &[(&str, V)], + kv: &[(&RedisKey, V)], field: &str, ttl: Option, ) -> CustomResult, errors::RedisError> @@ -387,7 +447,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn increment_fields_in_hash( &self, - key: &str, + key: &RedisKey, fields_to_increment: &[(T, i64)], ) -> CustomResult, errors::RedisError> where @@ -397,7 +457,7 @@ impl super::RedisConnectionPool { for (field, increment) in fields_to_increment.iter() { values_after_increment.push( self.pool - .hincrby(self.add_prefix(key), field.to_string(), *increment) + .hincrby(key.tenant_aware_key(self), field.to_string(), *increment) .await .change_context(errors::RedisError::IncrementHashFieldFailed)?, ) @@ -409,14 +469,14 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn hscan( &self, - key: &str, + key: &RedisKey, pattern: &str, count: Option, ) -> CustomResult, errors::RedisError> { Ok(self .pool .next() - .hscan::<&str, &str>(&self.add_prefix(key), pattern, count) + .hscan::<&str, &str>(&key.tenant_aware_key(self), pattern, count) .filter_map(|value| async move { match value { Ok(mut v) => { @@ -440,14 +500,14 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn scan( &self, - pattern: &str, + pattern: &RedisKey, count: Option, scan_type: Option, ) -> CustomResult, errors::RedisError> { Ok(self .pool .next() - .scan(&self.add_prefix(pattern), count, scan_type) + .scan(pattern.tenant_aware_key(self), count, scan_type) .filter_map(|value| async move { match value { Ok(mut v) => { @@ -471,7 +531,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn hscan_and_deserialize( &self, - key: &str, + key: &RedisKey, pattern: &str, count: Option, ) -> CustomResult, errors::RedisError> @@ -491,33 +551,69 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn get_hash_field( &self, - key: &str, + key: &RedisKey, field: &str, ) -> CustomResult where V: FromRedis + Unpin + Send + 'static, { - self.pool - .hget(self.add_prefix(key), field) + match self + .pool + .hget(key.tenant_aware_key(self), field) .await .change_context(errors::RedisError::GetHashFieldFailed) + { + Ok(v) => Ok(v), + Err(_err) => { + #[cfg(feature = "multitenancy_fallback")] + { + self.pool + .hget(key.tenant_unaware_key(self), field) + .await + .change_context(errors::RedisError::GetHashFieldFailed) + } + + #[cfg(not(feature = "multitenancy_fallback"))] + { + Err(_err) + } + } + } } #[instrument(level = "DEBUG", skip(self))] - pub async fn get_hash_fields(&self, key: &str) -> CustomResult + pub async fn get_hash_fields(&self, key: &RedisKey) -> CustomResult where V: FromRedis + Unpin + Send + 'static, { - self.pool - .hgetall(self.add_prefix(key)) + match self + .pool + .hgetall(key.tenant_aware_key(self)) .await .change_context(errors::RedisError::GetHashFieldFailed) + { + Ok(v) => Ok(v), + Err(_err) => { + #[cfg(feature = "multitenancy_fallback")] + { + self.pool + .hgetall(key.tenant_unaware_key(self)) + .await + .change_context(errors::RedisError::GetHashFieldFailed) + } + + #[cfg(not(feature = "multitenancy_fallback"))] + { + Err(_err) + } + } + } } #[instrument(level = "DEBUG", skip(self))] pub async fn get_hash_field_and_deserialize( &self, - key: &str, + key: &RedisKey, field: &str, type_name: &'static str, ) -> CustomResult @@ -538,7 +634,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn sadd( &self, - key: &str, + key: &RedisKey, members: V, ) -> CustomResult where @@ -546,7 +642,7 @@ impl super::RedisConnectionPool { V::Error: Into + Send, { self.pool - .sadd(self.add_prefix(key), members) + .sadd(key.tenant_aware_key(self), members) .await .change_context(errors::RedisError::SetAddMembersFailed) } @@ -554,7 +650,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn stream_append_entry( &self, - stream: &str, + stream: &RedisKey, entry_id: &RedisEntryId, fields: F, ) -> CustomResult<(), errors::RedisError> @@ -563,7 +659,7 @@ impl super::RedisConnectionPool { F::Error: Into + Send + Sync, { self.pool - .xadd(self.add_prefix(stream), false, None, entry_id, fields) + .xadd(stream.tenant_aware_key(self), false, None, entry_id, fields) .await .change_context(errors::RedisError::StreamAppendFailed) } @@ -571,14 +667,14 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn stream_delete_entries( &self, - stream: &str, + stream: &RedisKey, ids: Ids, ) -> CustomResult where Ids: Into + Debug + Send + Sync, { self.pool - .xdel(self.add_prefix(stream), ids) + .xdel(stream.tenant_aware_key(self), ids) .await .change_context(errors::RedisError::StreamDeleteFailed) } @@ -586,7 +682,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn stream_trim_entries( &self, - stream: &str, + stream: &RedisKey, xcap: C, ) -> CustomResult where @@ -594,7 +690,7 @@ impl super::RedisConnectionPool { C::Error: Into + Send + Sync, { self.pool - .xtrim(self.add_prefix(stream), xcap) + .xtrim(stream.tenant_aware_key(self), xcap) .await .change_context(errors::RedisError::StreamTrimFailed) } @@ -602,7 +698,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn stream_acknowledge_entries( &self, - stream: &str, + stream: &RedisKey, group: &str, ids: Ids, ) -> CustomResult @@ -610,15 +706,18 @@ impl super::RedisConnectionPool { Ids: Into + Debug + Send + Sync, { self.pool - .xack(self.add_prefix(stream), group, ids) + .xack(stream.tenant_aware_key(self), group, ids) .await .change_context(errors::RedisError::StreamAcknowledgeFailed) } #[instrument(level = "DEBUG", skip(self))] - pub async fn stream_get_length(&self, stream: &str) -> CustomResult { + pub async fn stream_get_length( + &self, + stream: &RedisKey, + ) -> CustomResult { self.pool - .xlen(self.add_prefix(stream)) + .xlen(stream.tenant_aware_key(self)) .await .change_context(errors::RedisError::GetLengthFailed) } @@ -631,10 +730,10 @@ impl super::RedisConnectionPool { let res = multiple_keys .inner() .iter() - .filter_map(|key| key.as_str()) - .map(|k| self.add_prefix(k)) - .map(RedisKey::from) + .filter_map(|key| key.as_str().map(RedisKey::from)) + .map(|k: RedisKey| k.tenant_aware_key(self)) .collect::>(); + MultipleKeys::from(res) } @@ -710,7 +809,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn append_elements_to_list( &self, - key: &str, + key: &RedisKey, elements: V, ) -> CustomResult<(), errors::RedisError> where @@ -718,7 +817,7 @@ impl super::RedisConnectionPool { V::Error: Into + Send, { self.pool - .rpush(self.add_prefix(key), elements) + .rpush(key.tenant_aware_key(self), elements) .await .change_context(errors::RedisError::AppendElementsToListFailed) } @@ -726,20 +825,20 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn get_list_elements( &self, - key: &str, + key: &RedisKey, start: i64, stop: i64, ) -> CustomResult, errors::RedisError> { self.pool - .lrange(self.add_prefix(key), start, stop) + .lrange(key.tenant_aware_key(self), start, stop) .await .change_context(errors::RedisError::GetListElementsFailed) } #[instrument(level = "DEBUG", skip(self))] - pub async fn get_list_length(&self, key: &str) -> CustomResult { + pub async fn get_list_length(&self, key: &RedisKey) -> CustomResult { self.pool - .llen(self.add_prefix(key)) + .llen(key.tenant_aware_key(self)) .await .change_context(errors::RedisError::GetListLengthFailed) } @@ -747,11 +846,11 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn lpop_list_elements( &self, - key: &str, + key: &RedisKey, count: Option, ) -> CustomResult, errors::RedisError> { self.pool - .lpop(self.add_prefix(key), count) + .lpop(key.tenant_aware_key(self), count) .await .change_context(errors::RedisError::PopListElementsFailed) } @@ -761,7 +860,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn consumer_group_create( &self, - stream: &str, + stream: &RedisKey, group: &str, id: &RedisEntryId, ) -> CustomResult<(), errors::RedisError> { @@ -774,7 +873,7 @@ impl super::RedisConnectionPool { } self.pool - .xgroup_create(self.add_prefix(stream), group, id, true) + .xgroup_create(stream.tenant_aware_key(self), group, id, true) .await .change_context(errors::RedisError::ConsumerGroupCreateFailed) } @@ -782,11 +881,11 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn consumer_group_destroy( &self, - stream: &str, + stream: &RedisKey, group: &str, ) -> CustomResult { self.pool - .xgroup_destroy(self.add_prefix(stream), group) + .xgroup_destroy(stream.tenant_aware_key(self), group) .await .change_context(errors::RedisError::ConsumerGroupDestroyFailed) } @@ -795,12 +894,12 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn consumer_group_delete_consumer( &self, - stream: &str, + stream: &RedisKey, group: &str, consumer: &str, ) -> CustomResult { self.pool - .xgroup_delconsumer(self.add_prefix(stream), group, consumer) + .xgroup_delconsumer(stream.tenant_aware_key(self), group, consumer) .await .change_context(errors::RedisError::ConsumerGroupRemoveConsumerFailed) } @@ -808,12 +907,12 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn consumer_group_set_last_id( &self, - stream: &str, + stream: &RedisKey, group: &str, id: &RedisEntryId, ) -> CustomResult { self.pool - .xgroup_setid(self.add_prefix(stream), group, id) + .xgroup_setid(stream.tenant_aware_key(self), group, id) .await .change_context(errors::RedisError::ConsumerGroupSetIdFailed) } @@ -821,7 +920,7 @@ impl super::RedisConnectionPool { #[instrument(level = "DEBUG", skip(self))] pub async fn consumer_group_set_message_owner( &self, - stream: &str, + stream: &RedisKey, group: &str, consumer: &str, min_idle_time: u64, @@ -833,7 +932,7 @@ impl super::RedisConnectionPool { { self.pool .xclaim( - self.add_prefix(stream), + stream.tenant_aware_key(self), group, consumer, min_idle_time, @@ -888,11 +987,15 @@ mod tests { // Act let result1 = redis_conn - .consumer_group_create("TEST1", "GTEST", &RedisEntryId::AutoGeneratedID) + .consumer_group_create(&"TEST1".into(), "GTEST", &RedisEntryId::AutoGeneratedID) .await; let result2 = redis_conn - .consumer_group_create("TEST3", "GTEST", &RedisEntryId::UndeliveredEntryID) + .consumer_group_create( + &"TEST3".into(), + "GTEST", + &RedisEntryId::UndeliveredEntryID, + ) .await; // Assert Setup @@ -914,10 +1017,10 @@ mod tests { let pool = RedisConnectionPool::new(&RedisSettings::default()) .await .expect("failed to create redis connection pool"); - let _ = pool.set_key("key", "value".to_string()).await; + let _ = pool.set_key(&"key".into(), "value".to_string()).await; // Act - let result = pool.delete_key("key").await; + let result = pool.delete_key(&"key".into()).await; // Assert setup result.is_ok() @@ -938,7 +1041,7 @@ mod tests { .expect("failed to create redis connection pool"); // Act - let result = pool.delete_key("key not exists").await; + let result = pool.delete_key(&"key not exists".into()).await; // Assert Setup result.is_ok() diff --git a/crates/redis_interface/src/types.rs b/crates/redis_interface/src/types.rs index 92429f617d1..848996deb4a 100644 --- a/crates/redis_interface/src/types.rs +++ b/crates/redis_interface/src/types.rs @@ -4,7 +4,7 @@ use common_utils::errors::CustomResult; use fred::types::RedisValue as FredRedisValue; -use crate::errors; +use crate::{errors, RedisConnectionPool}; pub struct RedisValue { inner: FredRedisValue, @@ -293,3 +293,24 @@ impl fred::types::FromRedis for SaddReply { } } } + +#[derive(Debug)] +pub struct RedisKey(String); + +impl RedisKey { + pub fn tenant_aware_key(&self, pool: &RedisConnectionPool) -> String { + pool.add_prefix(&self.0) + } + + pub fn tenant_unaware_key(&self, _pool: &RedisConnectionPool) -> String { + self.0.clone() + } +} + +impl> From for RedisKey { + fn from(value: T) -> Self { + let value = value.as_ref(); + + Self(value.to_string()) + } +} diff --git a/crates/router/src/core/api_locking.rs b/crates/router/src/core/api_locking.rs index 9b45fecee4e..7043a090b2b 100644 --- a/crates/router/src/core/api_locking.rs +++ b/crates/router/src/core/api_locking.rs @@ -79,7 +79,7 @@ impl LockAction { for _retry in 0..lock_retries { let redis_lock_result = redis_conn .set_key_if_not_exists_with_expiry( - redis_locking_key.as_str(), + &redis_locking_key.as_str().into(), state.get_request_id(), Some(i64::from(redis_lock_expiry_seconds)), ) @@ -134,12 +134,15 @@ impl LockAction { let redis_locking_key = input.get_redis_locking_key(merchant_id); match redis_conn - .get_key::>(&redis_locking_key) + .get_key::>(&redis_locking_key.as_str().into()) .await { Ok(val) => { if val == state.get_request_id() { - match redis_conn.delete_key(redis_locking_key.as_str()).await { + match redis_conn + .delete_key(&redis_locking_key.as_str().into()) + .await + { Ok(redis::types::DelReply::KeyDeleted) => { logger::info!("Lock freed for locking input {:?}", input); tracing::Span::current() diff --git a/crates/router/src/core/health_check.rs b/crates/router/src/core/health_check.rs index 31e8cc75f5b..2e5c2956013 100644 --- a/crates/router/src/core/health_check.rs +++ b/crates/router/src/core/health_check.rs @@ -52,21 +52,21 @@ impl HealthCheckInterface for app::SessionState { .change_context(errors::HealthCheckRedisError::RedisConnectionError)?; redis_conn - .serialize_and_set_key_with_expiry("test_key", "test_value", 30) + .serialize_and_set_key_with_expiry(&"test_key".into(), "test_value", 30) .await .change_context(errors::HealthCheckRedisError::SetFailed)?; logger::debug!("Redis set_key was successful"); redis_conn - .get_key::<()>("test_key") + .get_key::<()>(&"test_key".into()) .await .change_context(errors::HealthCheckRedisError::GetFailed)?; logger::debug!("Redis get_key was successful"); redis_conn - .delete_key("test_key") + .delete_key(&"test_key".into()) .await .change_context(errors::HealthCheckRedisError::DeleteFailed)?; diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 5bd2c07073e..7be645a81c7 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -3716,7 +3716,7 @@ pub async fn list_payment_methods( let redis_expiry = state.conf.payment_method_auth.get_inner().redis_expiry; if let Some(rc) = redis_conn { - rc.serialize_and_set_key_with_expiry(pm_auth_key.as_str(), val, redis_expiry) + rc.serialize_and_set_key_with_expiry(&pm_auth_key.as_str().into(), val, redis_expiry) .await .attach_printable("Failed to store pm auth data in redis") .unwrap_or_else(|error| { @@ -5030,7 +5030,7 @@ pub async fn list_customer_payment_method( ); redis_conn - .set_key_with_expiry(&key, pm_metadata.1, intent_fulfillment_time) + .set_key_with_expiry(&key.into(), pm_metadata.1, intent_fulfillment_time) .await .change_context(errors::StorageError::KVError) .change_context(errors::ApiErrorResponse::InternalServerError) diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 65ef16cb449..300f1e3054f 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1043,7 +1043,7 @@ pub async fn create_tokenize( redis_conn .set_key_if_not_exists_with_expiry( - redis_key.as_str(), + &redis_key.as_str().into(), bytes::Bytes::from(encrypted_payload), Some(i64::from(consts::LOCKER_REDIS_EXPIRY_SECONDS)), ) @@ -1089,7 +1089,9 @@ pub async fn get_tokenized_data( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get redis connection")?; - let response = redis_conn.get_key::(redis_key.as_str()).await; + let response = redis_conn + .get_key::(&redis_key.as_str().into()) + .await; match response { Ok(resp) => { @@ -1150,7 +1152,7 @@ pub async fn delete_tokenized_data( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get redis connection")?; - let response = redis_conn.delete_key(redis_key.as_str()).await; + let response = redis_conn.delete_key(&redis_key.as_str().into()).await; match response { Ok(redis_interface::DelReply::KeyDeleted) => Ok(()), diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index ceeb384ed21..4766dcda6a1 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -2363,7 +2363,7 @@ impl PaymentRedirectFlow for PaymentAuthenticateCompleteAuthorize { .attach_printable("Failed to get redis connection")?; redis_conn .set_key_with_expiry( - &poll_id, + &poll_id.into(), api_models::poll::PollStatus::Pending.to_string(), crate::consts::POLL_ID_TTL, ) @@ -4118,7 +4118,7 @@ async fn decide_payment_method_tokenize_action( ); let connector_token_option = redis_conn - .get_key::>(&key) + .get_key::>(&key.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch the token from redis")?; @@ -6705,7 +6705,7 @@ pub async fn get_extended_card_info( let key = helpers::get_redis_key_for_extended_card_info(&merchant_id, &payment_id); let payload = redis_conn - .get_key::(&key) + .get_key::(&key.into()) .await .change_context(errors::ApiErrorResponse::ExtendedCardInfoNotFound)?; diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 93769c71e6c..797aadbbc05 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2289,7 +2289,7 @@ pub async fn retrieve_payment_token_data( ); let token_data_string = redis_conn - .get_key::>(&key) + .get_key::>(&key.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch the token from redis")? @@ -3781,7 +3781,7 @@ pub async fn insert_merchant_connector_creds_to_config( redis .serialize_and_set_key_with_expiry( - key.as_str(), + &key.as_str().into(), &encoded_data.peek(), consts::CONNECTOR_CREDS_TOKEN_TTL, ) @@ -3897,7 +3897,7 @@ pub async fn get_merchant_connector_account( .attach_printable("Failed to get redis connection") .async_and_then(|redis| async move { redis - .get_and_deserialize_key(key.clone().as_str(), "String") + .get_and_deserialize_key(&key.as_str().into(), "String") .await .change_context( errors::ApiErrorResponse::MerchantConnectorAccountNotFound { @@ -5843,7 +5843,7 @@ pub async fn get_payment_method_details_from_payment_token( .get_required_value("payment_method")?, ); let token_data_string = redis_conn - .get_key::>(&key) + .get_key::>(&key.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch the token from redis")? diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index a87fab9b17a..da68ee27fd9 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1244,7 +1244,7 @@ impl Domain> for redis_conn .set_key_with_expiry( - &key, + &key.into(), encrypted_payload.clone(), (*merchant_config.ttl_in_secs).into(), ) diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 80813ac29b5..13f4de956ec 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -325,7 +325,11 @@ impl SurchargeMetadata { .get_order_fulfillment_time() .unwrap_or(router_consts::DEFAULT_FULFILLMENT_TIME); redis_conn - .set_hash_fields(&redis_key, value_list, Some(intent_fulfillment_time)) + .set_hash_fields( + &redis_key.as_str().into(), + value_list, + Some(intent_fulfillment_time), + ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to write to redis")?; @@ -347,7 +351,11 @@ impl SurchargeMetadata { let redis_key = Self::get_surcharge_metadata_redis_key(payment_attempt_id); let value_key = Self::get_surcharge_details_redis_hashset_key(&surcharge_key); let result = redis_conn - .get_hash_field_and_deserialize(&redis_key, &value_key, "SurchargeDetails") + .get_hash_field_and_deserialize( + &redis_key.as_str().into(), + &value_key, + "SurchargeDetails", + ) .await; logger::debug!( "Surcharge result fetched from redis with key = {} and {}", diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 060fc64f891..5ebdea0de67 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -79,7 +79,7 @@ pub async fn make_payout_method_data( .attach_printable("Failed to get redis connection")?; let hyperswitch_token = redis_conn - .get_key::>(&key) + .get_key::>(&key.into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch the token from redis")? diff --git a/crates/router/src/core/pm_auth.rs b/crates/router/src/core/pm_auth.rs index afba73e839c..0577b532cf0 100644 --- a/crates/router/src/core/pm_auth.rs +++ b/crates/router/src/core/pm_auth.rs @@ -63,7 +63,7 @@ pub async fn create_link_token( let pm_auth_key = payload.payment_id.get_pm_auth_key(); redis_conn - .exists::>(&pm_auth_key) + .exists::>(&pm_auth_key.as_str().into()) .await .change_context(ApiErrorResponse::InvalidRequestData { message: "Incorrect payment_id provided in request".to_string(), @@ -77,7 +77,7 @@ pub async fn create_link_token( let pm_auth_configs = redis_conn .get_and_deserialize_key::>( - pm_auth_key.as_str(), + &pm_auth_key.as_str().into(), "Vec", ) .await @@ -772,7 +772,7 @@ async fn get_selected_config_from_redis( let pm_auth_key = payload.payment_id.get_pm_auth_key(); redis_conn - .exists::>(&pm_auth_key) + .exists::>(&pm_auth_key.as_str().into()) .await .change_context(ApiErrorResponse::InvalidRequestData { message: "Incorrect payment_id provided in request".to_string(), @@ -786,7 +786,7 @@ async fn get_selected_config_from_redis( let pm_auth_configs = redis_conn .get_and_deserialize_key::>( - pm_auth_key.as_str(), + &pm_auth_key.as_str().into(), "Vec", ) .await diff --git a/crates/router/src/core/poll.rs b/crates/router/src/core/poll.rs index f0a464be287..dd468a26a70 100644 --- a/crates/router/src/core/poll.rs +++ b/crates/router/src/core/poll.rs @@ -23,7 +23,7 @@ pub async fn retrieve_poll_status( // prepend 'poll_{merchant_id}_' to restrict access to only fetching Poll IDs, as this is a freely passed string in the request let poll_id = super::utils::get_poll_id(merchant_account.get_id(), request_poll_id.clone()); let redis_value = redis_conn - .get_key::>(poll_id.as_str()) + .get_key::>(&poll_id.as_str().into()) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable_lazy(|| { diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 4c1a5aeb780..71130f247ad 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -1357,7 +1357,7 @@ async fn external_authentication_incoming_webhook_flow( .attach_printable("Failed to get redis connection")?; redis_conn .set_key_without_modifying_ttl( - &poll_id, + &poll_id.into(), api_models::poll::PollStatus::Completed.to_string(), ) .await diff --git a/crates/router/src/db/ephemeral_key.rs b/crates/router/src/db/ephemeral_key.rs index a77995e4e58..b3e33cd777f 100644 --- a/crates/router/src/db/ephemeral_key.rs +++ b/crates/router/src/db/ephemeral_key.rs @@ -103,7 +103,10 @@ mod storage { .get_redis_conn() .map_err(Into::::into)? .serialize_and_set_multiple_hash_field_if_not_exist( - &[(&secret_key, &created_ek), (&id_key, &created_ek)], + &[ + (&secret_key.as_str().into(), &created_ek), + (&id_key.as_str().into(), &created_ek), + ], "ephkey", None, ) @@ -120,12 +123,12 @@ mod storage { let expire_at = expires.assume_utc().unix_timestamp(); self.get_redis_conn() .map_err(Into::::into)? - .set_expire_at(&secret_key, expire_at) + .set_expire_at(&secret_key.into(), expire_at) .await .change_context(errors::StorageError::KVError)?; self.get_redis_conn() .map_err(Into::::into)? - .set_expire_at(&id_key, expire_at) + .set_expire_at(&id_key.into(), expire_at) .await .change_context(errors::StorageError::KVError)?; Ok(created_ek) @@ -161,8 +164,8 @@ mod storage { .map_err(Into::::into)? .serialize_and_set_multiple_hash_field_if_not_exist( &[ - (&secret_key, &created_ephemeral_key), - (&id_key, &created_ephemeral_key), + (&secret_key.as_str().into(), &created_ephemeral_key), + (&id_key.as_str().into(), &created_ephemeral_key), ], "ephkey", None, @@ -180,12 +183,12 @@ mod storage { let expire_at = expires.assume_utc().unix_timestamp(); self.get_redis_conn() .map_err(Into::::into)? - .set_expire_at(&secret_key, expire_at) + .set_expire_at(&secret_key.into(), expire_at) .await .change_context(errors::StorageError::KVError)?; self.get_redis_conn() .map_err(Into::::into)? - .set_expire_at(&id_key, expire_at) + .set_expire_at(&id_key.into(), expire_at) .await .change_context(errors::StorageError::KVError)?; Ok(created_ephemeral_key) @@ -203,7 +206,7 @@ mod storage { let key = format!("epkey_{key}"); self.get_redis_conn() .map_err(Into::::into)? - .get_hash_field_and_deserialize(&key, "ephkey", "EphemeralKey") + .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKey") .await .change_context(errors::StorageError::KVError) } @@ -217,7 +220,7 @@ mod storage { let key = format!("epkey_{key}"); self.get_redis_conn() .map_err(Into::::into)? - .get_hash_field_and_deserialize(&key, "ephkey", "EphemeralKeyType") + .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKeyType") .await .change_context(errors::StorageError::KVError) } @@ -231,13 +234,13 @@ mod storage { self.get_redis_conn() .map_err(Into::::into)? - .delete_key(&format!("epkey_{}", &ek.id)) + .delete_key(&format!("epkey_{}", &ek.id).into()) .await .change_context(errors::StorageError::KVError)?; self.get_redis_conn() .map_err(Into::::into)? - .delete_key(&format!("epkey_{}", &ek.secret)) + .delete_key(&format!("epkey_{}", &ek.secret).into()) .await .change_context(errors::StorageError::KVError)?; Ok(ek) @@ -254,7 +257,7 @@ mod storage { self.get_redis_conn() .map_err(Into::::into)? - .delete_key(&redis_id_key) + .delete_key(&redis_id_key.as_str().into()) .await .map_err(|err| match err.current_context() { RedisError::NotFound => { @@ -265,7 +268,7 @@ mod storage { self.get_redis_conn() .map_err(Into::::into)? - .delete_key(&secret_key) + .delete_key(&secret_key.as_str().into()) .await .map_err(|err| match err.current_context() { RedisError::NotFound => { diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index ba654b4fce3..34b23a833ac 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -59,7 +59,7 @@ impl ConnectorAccessToken for Store { let maybe_token = self .get_redis_conn() .map_err(Into::::into)? - .get_key::>>(&key) + .get_key::>>(&key.into()) .await .change_context(errors::StorageError::KVError) .attach_printable("DB error when getting access token")?; @@ -88,7 +88,7 @@ impl ConnectorAccessToken for Store { .change_context(errors::StorageError::SerializationFailed)?; self.get_redis_conn() .map_err(Into::::into)? - .set_key_with_expiry(&key, serialized_access_token, access_token.expires) + .set_key_with_expiry(&key.into(), serialized_access_token, access_token.expires) .await .change_context(errors::StorageError::KVError) } diff --git a/crates/router/src/routes/dummy_connector/core.rs b/crates/router/src/routes/dummy_connector/core.rs index aadd7dcaa72..cafb6b58bca 100644 --- a/crates/router/src/routes/dummy_connector/core.rs +++ b/crates/router/src/routes/dummy_connector/core.rs @@ -109,7 +109,7 @@ pub async fn payment_complete( .change_context(errors::DummyConnectorErrors::InternalServerError) .attach_printable("Failed to get redis connection")?; - let _ = redis_conn.delete_key(req.attempt_id.as_str()).await; + let _ = redis_conn.delete_key(&req.attempt_id.as_str().into()).await; if let Ok(payment_data) = payment_data { let updated_payment_data = types::DummyConnectorPaymentData { @@ -220,7 +220,7 @@ pub async fn refund_data( .attach_printable("Failed to get redis connection")?; let refund_data = redis_conn .get_and_deserialize_key::( - refund_id.as_str(), + &refund_id.as_str().into(), "DummyConnectorRefundResponse", ) .await diff --git a/crates/router/src/routes/dummy_connector/utils.rs b/crates/router/src/routes/dummy_connector/utils.rs index 6211c3abf80..edbe6d9dddc 100644 --- a/crates/router/src/routes/dummy_connector/utils.rs +++ b/crates/router/src/routes/dummy_connector/utils.rs @@ -38,7 +38,7 @@ pub async fn store_data_in_redis( .attach_printable("Failed to get redis connection")?; redis_conn - .serialize_and_set_key_with_expiry(&key, data, ttl) + .serialize_and_set_key_with_expiry(&key.into(), data, ttl) .await .change_context(errors::DummyConnectorErrors::PaymentStoringError) .attach_printable("Failed to add data in redis")?; @@ -57,7 +57,7 @@ pub async fn get_payment_data_from_payment_id( redis_conn .get_and_deserialize_key::( - payment_id.as_str(), + &payment_id.as_str().into(), "types DummyConnectorPaymentData", ) .await @@ -75,12 +75,12 @@ pub async fn get_payment_data_by_attempt_id( .attach_printable("Failed to get redis connection")?; redis_conn - .get_and_deserialize_key::(attempt_id.as_str(), "String") + .get_and_deserialize_key::(&attempt_id.as_str().into(), "String") .await .async_and_then(|payment_id| async move { redis_conn .get_and_deserialize_key::( - payment_id.as_str(), + &payment_id.as_str().into(), "DummyConnectorPaymentData", ) .await diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index c8e6303562c..5f9fe729eeb 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -980,7 +980,11 @@ impl ParentPaymentMethodToken { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get redis connection")?; redis_conn - .serialize_and_set_key_with_expiry(&self.key_for_token, token, fulfillment_time) + .serialize_and_set_key_with_expiry( + &self.key_for_token.as_str().into(), + token, + fulfillment_time, + ) .await .change_context(errors::StorageError::KVError) .change_context(errors::ApiErrorResponse::InternalServerError) @@ -1004,7 +1008,10 @@ impl ParentPaymentMethodToken { .get_redis_conn() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to get redis connection")?; - match redis_conn.delete_key(&self.key_for_token).await { + match redis_conn + .delete_key(&self.key_for_token.as_str().into()) + .await + { Ok(_) => Ok(()), Err(err) => { { diff --git a/crates/router/src/services/authentication/blacklist.rs b/crates/router/src/services/authentication/blacklist.rs index da5f87a450e..6be6cc467bf 100644 --- a/crates/router/src/services/authentication/blacklist.rs +++ b/crates/router/src/services/authentication/blacklist.rs @@ -30,7 +30,7 @@ pub async fn insert_user_in_blacklist(state: &SessionState, user_id: &str) -> Us let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?; redis_conn .set_key_with_expiry( - user_blacklist_key.as_str(), + &user_blacklist_key.as_str().into(), date_time::now_unix_timestamp(), expiry, ) @@ -46,7 +46,7 @@ pub async fn insert_role_in_blacklist(state: &SessionState, role_id: &str) -> Us let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?; redis_conn .set_key_with_expiry( - role_blacklist_key.as_str(), + &role_blacklist_key.as_str().into(), date_time::now_unix_timestamp(), expiry, ) @@ -61,7 +61,7 @@ pub async fn insert_role_in_blacklist(state: &SessionState, role_id: &str) -> Us async fn invalidate_role_cache(state: &SessionState, role_id: &str) -> RouterResult<()> { let redis_conn = get_redis_connection(state)?; redis_conn - .delete_key(authz::get_cache_key_from_role_id(role_id).as_str()) + .delete_key(&authz::get_cache_key_from_role_id(role_id).as_str().into()) .await .map(|_| ()) .change_context(ApiErrorResponse::InternalServerError) @@ -76,7 +76,7 @@ pub async fn check_user_in_blacklist( let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?; let redis_conn = get_redis_connection(state)?; redis_conn - .get_key::>(token.as_str()) + .get_key::>(&token.as_str().into()) .await .change_context(ApiErrorResponse::InternalServerError) .map(|timestamp| timestamp > Some(token_issued_at)) @@ -91,7 +91,7 @@ pub async fn check_role_in_blacklist( let token_issued_at = expiry_to_i64(token_expiry - JWT_TOKEN_TIME_IN_SECS)?; let redis_conn = get_redis_connection(state)?; redis_conn - .get_key::>(token.as_str()) + .get_key::>(&token.as_str().into()) .await .change_context(ApiErrorResponse::InternalServerError) .map(|timestamp| timestamp > Some(token_issued_at)) @@ -104,7 +104,7 @@ pub async fn insert_email_token_in_blacklist(state: &SessionState, token: &str) let expiry = expiry_to_i64(EMAIL_TOKEN_TIME_IN_SECS).change_context(UserErrors::InternalServerError)?; redis_conn - .set_key_with_expiry(blacklist_key.as_str(), true, expiry) + .set_key_with_expiry(&blacklist_key.as_str().into(), true, expiry) .await .change_context(UserErrors::InternalServerError) } @@ -114,7 +114,7 @@ pub async fn check_email_token_in_blacklist(state: &SessionState, token: &str) - let redis_conn = get_redis_connection(state).change_context(UserErrors::InternalServerError)?; let blacklist_key = format!("{}{token}", EMAIL_TOKEN_BLACKLIST_PREFIX); let key_exists = redis_conn - .exists::<()>(blacklist_key.as_str()) + .exists::<()>(&blacklist_key.as_str().into()) .await .change_context(UserErrors::InternalServerError)?; diff --git a/crates/router/src/services/authorization.rs b/crates/router/src/services/authorization.rs index db3483f8164..0e66654b7b9 100644 --- a/crates/router/src/services/authorization.rs +++ b/crates/router/src/services/authorization.rs @@ -64,7 +64,7 @@ where let redis_conn = get_redis_connection(state)?; redis_conn - .get_and_deserialize_key(&get_cache_key_from_role_id(role_id), "RoleInfo") + .get_and_deserialize_key(&get_cache_key_from_role_id(role_id).into(), "RoleInfo") .await .change_context(ApiErrorResponse::InternalServerError) } @@ -103,7 +103,11 @@ where let redis_conn = get_redis_connection(state)?; redis_conn - .serialize_and_set_key_with_expiry(&get_cache_key_from_role_id(role_id), role_info, expiry) + .serialize_and_set_key_with_expiry( + &get_cache_key_from_role_id(role_id).into(), + role_info, + expiry, + ) .await .change_context(ApiErrorResponse::InternalServerError) } diff --git a/crates/router/src/services/openidconnect.rs b/crates/router/src/services/openidconnect.rs index 0c0f86431a1..44f95b42607 100644 --- a/crates/router/src/services/openidconnect.rs +++ b/crates/router/src/services/openidconnect.rs @@ -35,7 +35,7 @@ pub async fn get_authorization_url( // Save csrf & nonce as key value respectively let key = get_oidc_redis_key(csrf_token.secret()); get_redis_connection(&state)? - .set_key_with_expiry(&key, nonce.secret(), consts::user::REDIS_SSO_TTL) + .set_key_with_expiry(&key.into(), nonce.secret(), consts::user::REDIS_SSO_TTL) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to save csrf-nonce in redis")?; @@ -142,7 +142,7 @@ async fn get_nonce_from_redis( let redirect_state = redirect_state.clone().expose(); let key = get_oidc_redis_key(&redirect_state); redis_connection - .get_key::>(&key) + .get_key::>(&key.into()) .await .change_context(UserErrors::InternalServerError) .attach_printable("Error Fetching CSRF from redis")? diff --git a/crates/router/src/utils/currency.rs b/crates/router/src/utils/currency.rs index c10ebf3dac1..4af9f3865f4 100644 --- a/crates/router/src/utils/currency.rs +++ b/crates/router/src/utils/currency.rs @@ -438,7 +438,7 @@ async fn release_redis_lock( .store .get_redis_conn() .change_context(ForexCacheError::RedisConnectionError)? - .delete_key(REDIX_FOREX_CACHE_KEY) + .delete_key(&REDIX_FOREX_CACHE_KEY.into()) .await .change_context(ForexCacheError::RedisLockReleaseFailed) .attach_printable("Unable to release redis lock") @@ -452,7 +452,7 @@ async fn acquire_redis_lock(state: &SessionState) -> CustomResult>(&key) + .get_key::>(&key.into()) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get sso id from redis")? diff --git a/crates/router/src/utils/user/two_factor_auth.rs b/crates/router/src/utils/user/two_factor_auth.rs index 73d692a00e0..b4ced70d735 100644 --- a/crates/router/src/utils/user/two_factor_auth.rs +++ b/crates/router/src/utils/user/two_factor_auth.rs @@ -36,7 +36,7 @@ pub async fn check_totp_in_redis(state: &SessionState, user_id: &str) -> UserRes let redis_conn = super::get_redis_connection(state)?; let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id); redis_conn - .exists::<()>(&key) + .exists::<()>(&key.into()) .await .change_context(UserErrors::InternalServerError) } @@ -45,7 +45,7 @@ pub async fn check_recovery_code_in_redis(state: &SessionState, user_id: &str) - let redis_conn = super::get_redis_connection(state)?; let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); redis_conn - .exists::<()>(&key) + .exists::<()>(&key.into()) .await .change_context(UserErrors::InternalServerError) } @@ -55,7 +55,7 @@ pub async fn insert_totp_in_redis(state: &SessionState, user_id: &str) -> UserRe let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id); redis_conn .set_key_with_expiry( - key.as_str(), + &key.as_str().into(), common_utils::date_time::now_unix_timestamp(), state.conf.user.two_factor_auth_expiry_in_secs, ) @@ -71,7 +71,7 @@ pub async fn insert_totp_secret_in_redis( let redis_conn = super::get_redis_connection(state)?; redis_conn .set_key_with_expiry( - &get_totp_secret_key(user_id), + &get_totp_secret_key(user_id).into(), secret.peek(), consts::user::REDIS_TOTP_SECRET_TTL_IN_SECS, ) @@ -85,7 +85,7 @@ pub async fn get_totp_secret_from_redis( ) -> UserResult>> { let redis_conn = super::get_redis_connection(state)?; redis_conn - .get_key::>(&get_totp_secret_key(user_id)) + .get_key::>(&get_totp_secret_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|secret| secret.map(Into::into)) @@ -94,7 +94,7 @@ pub async fn get_totp_secret_from_redis( pub async fn delete_totp_secret_from_redis(state: &SessionState, user_id: &str) -> UserResult<()> { let redis_conn = super::get_redis_connection(state)?; redis_conn - .delete_key(&get_totp_secret_key(user_id)) + .delete_key(&get_totp_secret_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|_| ()) @@ -109,7 +109,7 @@ pub async fn insert_recovery_code_in_redis(state: &SessionState, user_id: &str) let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); redis_conn .set_key_with_expiry( - key.as_str(), + &key.as_str().into(), common_utils::date_time::now_unix_timestamp(), state.conf.user.two_factor_auth_expiry_in_secs, ) @@ -121,7 +121,7 @@ pub async fn delete_totp_from_redis(state: &SessionState, user_id: &str) -> User let redis_conn = super::get_redis_connection(state)?; let key = format!("{}{}", consts::user::REDIS_TOTP_PREFIX, user_id); redis_conn - .delete_key(&key) + .delete_key(&key.into()) .await .change_context(UserErrors::InternalServerError) .map(|_| ()) @@ -134,7 +134,7 @@ pub async fn delete_recovery_code_from_redis( let redis_conn = super::get_redis_connection(state)?; let key = format!("{}{}", consts::user::REDIS_RECOVERY_CODE_PREFIX, user_id); redis_conn - .delete_key(&key) + .delete_key(&key.into()) .await .change_context(UserErrors::InternalServerError) .map(|_| ()) @@ -159,7 +159,7 @@ pub async fn insert_totp_attempts_in_redis( let redis_conn = super::get_redis_connection(state)?; redis_conn .set_key_with_expiry( - &get_totp_attempts_key(user_id), + &get_totp_attempts_key(user_id).into(), user_totp_attempts, consts::user::REDIS_TOTP_ATTEMPTS_TTL_IN_SECS, ) @@ -169,7 +169,7 @@ pub async fn insert_totp_attempts_in_redis( pub async fn get_totp_attempts_from_redis(state: &SessionState, user_id: &str) -> UserResult { let redis_conn = super::get_redis_connection(state)?; redis_conn - .get_key::>(&get_totp_attempts_key(user_id)) + .get_key::>(&get_totp_attempts_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|v| v.unwrap_or(0)) @@ -183,7 +183,7 @@ pub async fn insert_recovery_code_attempts_in_redis( let redis_conn = super::get_redis_connection(state)?; redis_conn .set_key_with_expiry( - &get_recovery_code_attempts_key(user_id), + &get_recovery_code_attempts_key(user_id).into(), user_recovery_code_attempts, consts::user::REDIS_RECOVERY_CODE_ATTEMPTS_TTL_IN_SECS, ) @@ -197,7 +197,7 @@ pub async fn get_recovery_code_attempts_from_redis( ) -> UserResult { let redis_conn = super::get_redis_connection(state)?; redis_conn - .get_key::>(&get_recovery_code_attempts_key(user_id)) + .get_key::>(&get_recovery_code_attempts_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|v| v.unwrap_or(0)) @@ -209,7 +209,7 @@ pub async fn delete_totp_attempts_from_redis( ) -> UserResult<()> { let redis_conn = super::get_redis_connection(state)?; redis_conn - .delete_key(&get_totp_attempts_key(user_id)) + .delete_key(&get_totp_attempts_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|_| ()) @@ -221,7 +221,7 @@ pub async fn delete_recovery_code_attempts_from_redis( ) -> UserResult<()> { let redis_conn = super::get_redis_connection(state)?; redis_conn - .delete_key(&get_recovery_code_attempts_key(user_id)) + .delete_key(&get_recovery_code_attempts_key(user_id).into()) .await .change_context(UserErrors::InternalServerError) .map(|_| ()) diff --git a/crates/router/tests/cache.rs b/crates/router/tests/cache.rs index a1f85534b6b..7e1fab4bc5a 100644 --- a/crates/router/tests/cache.rs +++ b/crates/router/tests/cache.rs @@ -30,7 +30,7 @@ async fn invalidate_existing_cache_success() { .store .get_redis_conn() .unwrap() - .set_key(&cache_key.clone(), cache_key_value.clone()) + .set_key(&cache_key.clone().into(), cache_key_value.clone()) .await; let api_key = ("api-key", "test_admin"); diff --git a/crates/scheduler/src/db/queue.rs b/crates/scheduler/src/db/queue.rs index 748e1f489c2..80f2b8b4c92 100644 --- a/crates/scheduler/src/db/queue.rs +++ b/crates/scheduler/src/db/queue.rs @@ -70,7 +70,7 @@ impl QueueInterface for Store { id: &RedisEntryId, ) -> CustomResult<(), RedisError> { self.get_redis_conn()? - .consumer_group_create(stream, group, id) + .consumer_group_create(&stream.into(), group, id) .await } @@ -83,16 +83,16 @@ impl QueueInterface for Store { ) -> CustomResult { let conn = self.get_redis_conn()?.clone(); let is_lock_acquired = conn - .set_key_if_not_exists_with_expiry(lock_key, lock_val, None) + .set_key_if_not_exists_with_expiry(&lock_key.into(), lock_val, None) .await; Ok(match is_lock_acquired { - Ok(SetnxReply::KeySet) => match conn.set_expiry(lock_key, ttl).await { + Ok(SetnxReply::KeySet) => match conn.set_expiry(&lock_key.into(), ttl).await { Ok(()) => true, #[allow(unused_must_use)] Err(error) => { logger::error!(?error); - conn.delete_key(lock_key).await; + conn.delete_key(&lock_key.into()).await; false } }, @@ -108,7 +108,7 @@ impl QueueInterface for Store { } async fn release_pt_lock(&self, tag: &str, lock_key: &str) -> CustomResult { - let is_lock_released = self.get_redis_conn()?.delete_key(lock_key).await; + let is_lock_released = self.get_redis_conn()?.delete_key(&lock_key.into()).await; Ok(match is_lock_released { Ok(_del_reply) => true, Err(error) => { @@ -125,12 +125,12 @@ impl QueueInterface for Store { fields: Vec<(&str, String)>, ) -> CustomResult<(), RedisError> { self.get_redis_conn()? - .stream_append_entry(stream, entry_id, fields) + .stream_append_entry(&stream.into(), entry_id, fields) .await } async fn get_key(&self, key: &str) -> CustomResult, RedisError> { - self.get_redis_conn()?.get_key::>(key).await + self.get_redis_conn()?.get_key::>(&key.into()).await } } diff --git a/crates/scheduler/src/utils.rs b/crates/scheduler/src/utils.rs index 89328479537..51938e6c14d 100644 --- a/crates/scheduler/src/utils.rs +++ b/crates/scheduler/src/utils.rs @@ -231,13 +231,13 @@ pub async fn get_batches( let batches = batches.into_iter().flatten().collect::>(); let entry_ids = entry_ids.into_iter().flatten().collect::>(); - conn.stream_acknowledge_entries(stream_name, group_name, entry_ids.clone()) + conn.stream_acknowledge_entries(&stream_name.into(), group_name, entry_ids.clone()) .await .map_err(|error| { logger::error!(?error, "Error acknowledging batch in stream"); error.change_context(errors::ProcessTrackerError::BatchUpdateFailed) })?; - conn.stream_delete_entries(stream_name, entry_ids.clone()) + conn.stream_delete_entries(&stream_name.into(), entry_ids.clone()) .await .map_err(|error| { logger::error!(?error, "Error deleting batch from stream"); diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index e0722ef52ea..8a329010447 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -256,7 +256,7 @@ impl KVRouterStore { .cache_store .redis_conn .stream_append_entry( - &stream_name, + &stream_name.into(), &redis_interface::RedisEntryId::AutoGeneratedID, redis_entry .to_field_value_pairs(request_id, global_id) @@ -309,7 +309,7 @@ pub trait UniqueConstraints { let constraints = self.unique_constraints(); let sadd_result = redis_conn .sadd( - &format!("unique_constraint:{}", self.table_name()), + &format!("unique_constraint:{}", self.table_name()).into(), constraints, ) .await?; diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 323d3d6df25..8302b5bf933 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -299,11 +299,13 @@ where { let type_name = std::any::type_name::(); let key = key.as_ref(); - let redis_val = redis.get_and_deserialize_key::(key, type_name).await; + let redis_val = redis + .get_and_deserialize_key::(&key.into(), type_name) + .await; let get_data_set_redis = || async { let data = fun().await?; redis - .serialize_and_set_key(key, &data) + .serialize_and_set_key(&key.into(), &data) .await .change_context(StorageError::KVError)?; Ok::<_, Report>(data) @@ -380,7 +382,7 @@ pub async fn redact_from_redis_and_publish< let redis_keys_to_be_deleted = keys .clone() .into_iter() - .map(|val| val.get_key_without_prefix().to_owned()) + .map(|val| val.get_key_without_prefix().to_owned().into()) .collect::>(); let del_replies = redis_conn diff --git a/crates/storage_impl/src/redis/kv_store.rs b/crates/storage_impl/src/redis/kv_store.rs index 17974a3b9bc..6e1340abca5 100644 --- a/crates/storage_impl/src/redis/kv_store.rs +++ b/crates/storage_impl/src/redis/kv_store.rs @@ -181,7 +181,7 @@ where logger::debug!(kv_operation= %operation, value = ?value); redis_conn - .set_hash_fields(&key, value, Some(ttl.into())) + .set_hash_fields(&key.into(), value, Some(ttl.into())) .await?; store @@ -193,14 +193,14 @@ where KvOperation::HGet(field) => { let result = redis_conn - .get_hash_field_and_deserialize(&key, field, type_name) + .get_hash_field_and_deserialize(&key.into(), field, type_name) .await?; Ok(KvResult::HGet(result)) } KvOperation::Scan(pattern) => { let result: Vec = redis_conn - .hscan_and_deserialize(&key, pattern, None) + .hscan_and_deserialize(&key.into(), pattern, None) .await .and_then(|result| { if result.is_empty() { @@ -218,7 +218,7 @@ where value.check_for_constraints(&redis_conn).await?; let result = redis_conn - .serialize_and_set_hash_field_if_not_exist(&key, field, value, Some(ttl)) + .serialize_and_set_hash_field_if_not_exist(&key.into(), field, value, Some(ttl)) .await?; if matches!(result, redis_interface::HsetnxReply::KeySet) { @@ -235,7 +235,7 @@ where logger::debug!(kv_operation= %operation, value = ?value); let result = redis_conn - .serialize_and_set_key_if_not_exist(&key, value, Some(ttl.into())) + .serialize_and_set_key_if_not_exist(&key.into(), value, Some(ttl.into())) .await?; value.check_for_constraints(&redis_conn).await?; @@ -251,7 +251,9 @@ where } KvOperation::Get => { - let result = redis_conn.get_and_deserialize_key(&key, type_name).await?; + let result = redis_conn + .get_and_deserialize_key(&key.into(), type_name) + .await?; Ok(KvResult::Get(result)) } } From 4cf011f9886de419b48576f5d4ef77fdcfc2d4ad Mon Sep 17 00:00:00 2001 From: Nishant Joshi Date: Wed, 29 Jan 2025 00:38:31 +0530 Subject: [PATCH 004/133] chore: add stripe to network transaction id support (#7096) --- config/deployments/production.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 3537834fd07..90a34403637 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "adyen" +connector_list = "adyen,stripe" [payouts] payout_eligibility = true # Defaults the eligibility of a payout method to true in case connector does not provide checks for payout eligibility From 5ff57fa3374cd4bb6ff211057d7280b6fd1ea321 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 29 Jan 2025 01:36:24 +0530 Subject: [PATCH 005/133] refactor(router): prioritise `connector_mandate_id` over `network_transaction_id` during MITs (#7081) --- crates/router/src/core/payments.rs | 40 +++++++++++++++--------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 4766dcda6a1..e6cd8409343 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5799,26 +5799,7 @@ where .as_ref() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to find the merchant connector id")?; - if is_network_transaction_id_flow( - state, - is_connector_agnostic_mit_enabled, - connector_data.connector_name, - payment_method_info, - ) { - logger::info!("using network_transaction_id for MIT flow"); - let network_transaction_id = payment_method_info - .network_transaction_id - .as_ref() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to fetch the network transaction id")?; - - let mandate_reference_id = Some(payments_api::MandateReferenceId::NetworkMandateId( - network_transaction_id.to_string(), - )); - - connector_choice = Some((connector_data, mandate_reference_id.clone())); - break; - } else if connector_mandate_details + if connector_mandate_details .clone() .map(|connector_mandate_details| { connector_mandate_details.contains_key(merchant_connector_id) @@ -5868,6 +5849,25 @@ where break; } } + } else if is_network_transaction_id_flow( + state, + is_connector_agnostic_mit_enabled, + connector_data.connector_name, + payment_method_info, + ) { + logger::info!("using network_transaction_id for MIT flow"); + let network_transaction_id = payment_method_info + .network_transaction_id + .as_ref() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to fetch the network transaction id")?; + + let mandate_reference_id = Some(payments_api::MandateReferenceId::NetworkMandateId( + network_transaction_id.to_string(), + )); + + connector_choice = Some((connector_data, mandate_reference_id.clone())); + break; } else { continue; } From c02cb20b02501eb027e61e4f4a342eeda1fb9b70 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 29 Jan 2025 00:30:17 +0000 Subject: [PATCH 006/133] chore(version): 2025.01.29.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71c8ad039ec..2bcd929bcbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.01.29.0 + +### Bug Fixes + +- **multitenancy:** Add a fallback for get commands in redis ([#7043](https://github.com/juspay/hyperswitch/pull/7043)) ([`5707297`](https://github.com/juspay/hyperswitch/commit/5707297621538ccf47f7314ca564783d6f289317)) + +### Refactors + +- **currency_conversion:** Re frame the currency_conversion crate to make api calls on background thread ([#6906](https://github.com/juspay/hyperswitch/pull/6906)) ([`858866f`](https://github.com/juspay/hyperswitch/commit/858866f9f361c16b76ed79b42814b648f2050f08)) +- **router:** Prioritise `connector_mandate_id` over `network_transaction_id` during MITs ([#7081](https://github.com/juspay/hyperswitch/pull/7081)) ([`5ff57fa`](https://github.com/juspay/hyperswitch/commit/5ff57fa3374cd4bb6ff211057d7280b6fd1ea321)) + +### Miscellaneous Tasks + +- Fix `toml` format to address wasm build failure ([#6967](https://github.com/juspay/hyperswitch/pull/6967)) ([`ecab2b1`](https://github.com/juspay/hyperswitch/commit/ecab2b1f512eb7e78ca2e75c20b3adc753b97a2f)) +- Add stripe to network transaction id support ([#7096](https://github.com/juspay/hyperswitch/pull/7096)) ([`4cf011f`](https://github.com/juspay/hyperswitch/commit/4cf011f9886de419b48576f5d4ef77fdcfc2d4ad)) + +**Full Changelog:** [`2025.01.27.0...2025.01.29.0`](https://github.com/juspay/hyperswitch/compare/2025.01.27.0...2025.01.29.0) + +- - - + ## 2025.01.27.0 ### Bug Fixes From 275958af14d0eb4385c995308fbf958c6b620e4f Mon Sep 17 00:00:00 2001 From: Prajjwal Kumar Date: Wed, 29 Jan 2025 12:53:36 +0530 Subject: [PATCH 007/133] refactor(euclid): update proto file for elimination routing (#7032) --- proto/elimination_rate.proto | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/proto/elimination_rate.proto b/proto/elimination_rate.proto index c5f10597ade..d585dd2494b 100644 --- a/proto/elimination_rate.proto +++ b/proto/elimination_rate.proto @@ -28,8 +28,17 @@ message EliminationResponse { message LabelWithStatus { string label = 1; - bool is_eliminated = 2; - string bucket_name = 3; + EliminationInformation elimination_information = 2; +} + +message EliminationInformation { + BucketInformation entity = 1; + BucketInformation global = 2; +} + +message BucketInformation { + bool is_eliminated = 1; + repeated string bucket_name = 2; } // API-2 types @@ -64,4 +73,4 @@ message InvalidateBucketResponse { BUCKET_INVALIDATION_FAILED = 1; } InvalidationStatus status = 1; -} \ No newline at end of file +} From 5381eb992228164b552260c7ebb8a4cdbc1b3cb3 Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Wed, 29 Jan 2025 12:53:56 +0530 Subject: [PATCH 008/133] feat(router): add accept-language from request headers into browser-info (#7074) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 5 +++++ crates/common_utils/src/types.rs | 3 +++ .../src/router_request_types.rs | 2 ++ .../src/connector/netcetera/netcetera_types.rs | 16 ++++++++++++++-- .../src/connector/trustpay/transformers.rs | 1 + crates/router/src/routes/payments.rs | 15 ++++++++------- crates/router/src/routes/payments/helpers.rs | 11 ++++++++++- crates/router/tests/connectors/trustpay.rs | 1 + crates/router/tests/connectors/utils.rs | 1 + 9 files changed, 45 insertions(+), 10 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 892931256bc..13014a44e0e 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -5696,6 +5696,11 @@ "type": "string", "description": "The device model of the client", "nullable": true + }, + "accept_language": { + "type": "string", + "description": "Accept-language of the browser", + "nullable": true } } }, diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 9e71ca76733..4e88d5c0fde 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1408,6 +1408,9 @@ pub struct BrowserInformation { /// The device model of the client pub device_model: Option, + + /// Accept-language of the browser + pub accept_language: Option, } #[cfg(feature = "v2")] diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 2f8594662eb..4598fa5d1b5 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -489,6 +489,7 @@ pub struct BrowserInformation { pub os_type: Option, pub os_version: Option, pub device_model: Option, + pub accept_language: Option, } #[cfg(feature = "v2")] @@ -508,6 +509,7 @@ impl From for BrowserInformation { os_type: value.os_type, os_version: value.os_version, device_model: value.device_model, + accept_language: value.accept_language, } } } diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index a69798314ba..0ecbbf63e3e 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1372,6 +1372,15 @@ pub struct Browser { accept_language: Option>, } +// Split by comma and return the list of accept languages +// If Accept-Language is : fr-CH, fr;q=0.9, en;q=0.8, de;q=0.7, List should be [fr-CH, fr, en, de] +pub fn get_list_of_accept_languages(accept_language: String) -> Vec { + accept_language + .split(',') + .map(|lang| lang.split(';').next().unwrap_or(lang).trim().to_string()) + .collect() +} + impl From for Browser { fn from(value: crate::types::BrowserInformation) -> Self { Self { @@ -1388,8 +1397,11 @@ impl From for Browser { browser_user_agent: value.user_agent, challenge_window_size: Some(ChallengeWindowSizeEnum::FullScreen), browser_javascript_enabled: value.java_script_enabled, - // Hardcoding to "en" for now, as there's no accept_language in BrowserInformation - accept_language: Some(vec!["en".to_string()]), + // Default to ["en"] locale if accept_language is not provided + accept_language: value + .accept_language + .map(get_list_of_accept_languages) + .or(Some(vec!["en".to_string()])), } } } diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 83587609b3f..31c992a7f5a 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -401,6 +401,7 @@ impl TryFrom<&TrustpayRouterData<&types::PaymentsAuthorizeRouterData>> for Trust os_type: None, os_version: None, device_model: None, + accept_language: Some(browser_info.accept_language.unwrap_or("en".to_string())), }; let params = get_mandatory_fields(item.router_data)?; let amount = item.amount.to_owned(); diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index cd8aa1c3c81..16468aef74c 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -602,7 +602,14 @@ pub async fn payments_confirm( return http_not_implemented(); }; - if let Err(err) = helpers::populate_ip_into_browser_info(&req, &mut payload) { + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + if let Err(err) = helpers::populate_browser_info(&req, &mut payload, &header_payload) { return api::log_and_return_error_response(err); } @@ -610,12 +617,6 @@ pub async fn payments_confirm( tracing::Span::current().record("payment_id", payment_id.get_string_repr()); payload.payment_id = Some(payment_types::PaymentIdType::PaymentIntentId(payment_id)); payload.confirm = Some(true); - let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { - Ok(headers) => headers, - Err(err) => { - return api::log_and_return_error_response(err); - } - }; let (auth_type, auth_flow) = match auth::check_client_secret_and_get_auth(req.headers(), &payload) { diff --git a/crates/router/src/routes/payments/helpers.rs b/crates/router/src/routes/payments/helpers.rs index 529475a3584..39a836a8385 100644 --- a/crates/router/src/routes/payments/helpers.rs +++ b/crates/router/src/routes/payments/helpers.rs @@ -8,9 +8,10 @@ use crate::{ }; #[cfg(feature = "v1")] -pub fn populate_ip_into_browser_info( +pub fn populate_browser_info( req: &actix_web::HttpRequest, payload: &mut api::PaymentsRequest, + header_payload: &hyperswitch_domain_models::payments::HeaderPayload, ) -> RouterResult<()> { let mut browser_info: types::BrowserInformation = payload .browser_info @@ -34,6 +35,7 @@ pub fn populate_ip_into_browser_info( os_type: None, os_version: None, device_model: None, + accept_language: None, }); let ip_address = req @@ -59,6 +61,13 @@ pub fn populate_ip_into_browser_info( }) }); + // If the locale is present in the header payload, we will use it as the accept language + if header_payload.locale.is_some() { + browser_info.accept_language = browser_info + .accept_language + .or(header_payload.locale.clone()); + } + if let Some(api::MandateData { customer_acceptance: Some(api::CustomerAcceptance { diff --git a/crates/router/tests/connectors/trustpay.rs b/crates/router/tests/connectors/trustpay.rs index 2e5ea6d50f1..a5eb08c67e6 100644 --- a/crates/router/tests/connectors/trustpay.rs +++ b/crates/router/tests/connectors/trustpay.rs @@ -52,6 +52,7 @@ fn get_default_browser_info() -> BrowserInformation { os_type: None, os_version: None, device_model: None, + accept_language: Some("en".to_string()), } } diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 2cf3dc965fd..ca818e61753 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1022,6 +1022,7 @@ impl Default for BrowserInfoType { device_model: Some("Apple IPHONE 7".to_string()), os_type: Some("IOS or ANDROID".to_string()), os_version: Some("IOS 14.5".to_string()), + accept_language: Some("en".to_string()), }; Self(data) } From 337095bce8c57be9a9a2ff8356ca9b70917b9851 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Wed, 29 Jan 2025 13:25:52 +0530 Subject: [PATCH 009/133] chore: run clippy with default number of jobs in github workflows (#7088) --- .github/workflows/CI-pr.yml | 40 ++++++++++++------- .github/workflows/CI-push.yml | 40 +++++++++---------- .github/workflows/connector-sanity-tests.yml | 4 +- .../workflows/connector-ui-sanity-tests.yml | 4 +- .../workflows/postman-collection-runner.yml | 4 +- 5 files changed, 55 insertions(+), 37 deletions(-) diff --git a/.github/workflows/CI-pr.yml b/.github/workflows/CI-pr.yml index d01c26d5112..b337a179959 100644 --- a/.github/workflows/CI-pr.yml +++ b/.github/workflows/CI-pr.yml @@ -90,7 +90,7 @@ jobs: env: # Use `sccache` for caching compilation artifacts - RUSTC_WRAPPER: sccache + # RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -126,11 +126,16 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install sccache - uses: taiki-e/install-action@v2.33.28 - with: - tool: sccache - checksum: true + # - name: Install sccache + # uses: taiki-e/install-action@v2.33.28 + # with: + # tool: sccache + # checksum: true + + - name: Install rust cache + uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Install cargo-hack uses: taiki-e/install-action@v2.33.28 @@ -181,7 +186,7 @@ jobs: env: # Use `sccache` for caching compilation artifacts - RUSTC_WRAPPER: sccache + # RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -229,11 +234,16 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install sccache - uses: taiki-e/install-action@v2.33.28 - with: - tool: sccache - checksum: true + # - name: Install sccache + # uses: taiki-e/install-action@v2.33.28 + # with: + # tool: sccache + # checksum: true + + - name: Install rust cache + uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Install cargo-hack uses: taiki-e/install-action@v2.33.28 @@ -259,7 +269,7 @@ jobs: - name: Run clippy shell: bash - run: just clippy --jobs 2 + run: just clippy - name: Check Cargo.lock changed if: ${{ (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name) }} @@ -315,7 +325,9 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install rust cache - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Install just uses: taiki-e/install-action@v2.41.10 diff --git a/.github/workflows/CI-push.yml b/.github/workflows/CI-push.yml index e17ce46d850..306e6812594 100644 --- a/.github/workflows/CI-push.yml +++ b/.github/workflows/CI-push.yml @@ -43,7 +43,7 @@ jobs: env: # Use `sccache` for caching compilation artifacts - RUSTC_WRAPPER: sccache + # RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -78,15 +78,15 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install sccache - uses: taiki-e/install-action@v2.33.28 - with: - tool: sccache - checksum: true - - # - uses: Swatinem/rust-cache@v2.7.0 + # - name: Install sccache + # uses: taiki-e/install-action@v2.33.28 # with: - # save-if: ${{ github.event_name == 'push' }} + # tool: sccache + # checksum: true + + - uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: ${{ github.event_name == 'push' }} - name: Install cargo-hack uses: baptiste0928/cargo-install@v2.2.0 @@ -140,7 +140,7 @@ jobs: env: # Use `sccache` for caching compilation artifacts - RUSTC_WRAPPER: sccache + # RUSTC_WRAPPER: sccache RUSTFLAGS: "-D warnings" strategy: @@ -170,11 +170,11 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} - - name: Install sccache - uses: taiki-e/install-action@v2.33.28 - with: - tool: sccache - checksum: true + # - name: Install sccache + # uses: taiki-e/install-action@v2.33.28 + # with: + # tool: sccache + # checksum: true - name: Install cargo-hack uses: baptiste0928/cargo-install@v2.2.0 @@ -196,13 +196,13 @@ jobs: # with: # crate: cargo-nextest - # - uses: Swatinem/rust-cache@v2.7.0 - # with: - # save-if: ${{ github.event_name == 'push' }} + - uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: ${{ github.event_name == 'push' }} - name: Run clippy shell: bash - run: just clippy --jobs 2 + run: just clippy - name: Cargo hack if: ${{ github.event_name == 'push' }} @@ -249,7 +249,7 @@ jobs: repo-token: ${{ secrets.GITHUB_TOKEN }} - name: Install rust cache - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2.7.7 - name: Run cargo check enabling only the release and v2 features shell: bash diff --git a/.github/workflows/connector-sanity-tests.yml b/.github/workflows/connector-sanity-tests.yml index 48e6a946a45..1f10828de9d 100644 --- a/.github/workflows/connector-sanity-tests.yml +++ b/.github/workflows/connector-sanity-tests.yml @@ -86,7 +86,9 @@ jobs: with: toolchain: stable 2 weeks ago - - uses: Swatinem/rust-cache@v2.7.0 + - uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Decrypt connector auth file env: diff --git a/.github/workflows/connector-ui-sanity-tests.yml b/.github/workflows/connector-ui-sanity-tests.yml index f3d4635ab11..9cad6191f06 100644 --- a/.github/workflows/connector-ui-sanity-tests.yml +++ b/.github/workflows/connector-ui-sanity-tests.yml @@ -122,7 +122,9 @@ jobs: toolchain: stable - name: Build and Cache Rust Dependencies - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Install Diesel CLI with Postgres Support uses: baptiste0928/cargo-install@v2.2.0 diff --git a/.github/workflows/postman-collection-runner.yml b/.github/workflows/postman-collection-runner.yml index b8ce65f4b6c..bc6bd7849fe 100644 --- a/.github/workflows/postman-collection-runner.yml +++ b/.github/workflows/postman-collection-runner.yml @@ -92,7 +92,9 @@ jobs: - name: Build and Cache Rust Dependencies if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} - uses: Swatinem/rust-cache@v2.7.0 + uses: Swatinem/rust-cache@v2.7.7 + with: + save-if: false - name: Install Protoc if: ${{ ((github.event_name == 'pull_request') && (github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name)) || (github.event_name == 'merge_group')}} From ad5491f15bd8f61b2a918f584fe85132986176ad Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Wed, 29 Jan 2025 16:03:21 +0530 Subject: [PATCH 010/133] feat(connector): add template code for chargebee (#7036) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + crates/common_enums/src/connector_enums.rs | 3 + .../hyperswitch_connectors/src/connectors.rs | 22 +- .../src/connectors/chargebee.rs | 568 ++++++++++++++++++ .../src/connectors/chargebee/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 34 ++ .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 4 +- .../connector_integration_v2_impls.rs | 3 + crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 1 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/chargebee.rs | 421 +++++++++++++ crates/router/tests/connectors/main.rs | 1 + .../router/tests/connectors/sample_auth.toml | 5 +- crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 1 + scripts/add_connector.sh | 2 +- 24 files changed, 1313 insertions(+), 14 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/chargebee.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs create mode 100644 crates/router/tests/connectors/chargebee.rs diff --git a/config/config.example.toml b/config/config.example.toml index ecc2d2127ce..1d4507126fe 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -193,6 +193,7 @@ bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" boku.base_url = "https://$-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index b6e487e5a3b..d606f192e4e 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -39,6 +39,7 @@ bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" boku.base_url = "https://$-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 90a34403637..ff5abd3fb03 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -43,6 +43,7 @@ bluesnap.secondary_base_url = "https://pay.bluesnap.com/" boku.base_url = "https://country-api4-stage.boku.com" braintree.base_url = "https://payments.braintree-api.com/graphql" cashtocode.base_url = "https://cluster14.api.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business.cryptopay.me/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 7242f263672..208621433bb 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -44,6 +44,7 @@ boku.base_url = "https://$-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" checkout.base_url = "https://api.sandbox.checkout.com/" +chargebee.base_url = "https://$.chargebee.com/api/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" diff --git a/config/development.toml b/config/development.toml index 6bce5cb89d7..b293151a988 100644 --- a/config/development.toml +++ b/config/development.toml @@ -212,6 +212,7 @@ bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" boku.base_url = "https://$-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 293e9a313e9..656d4fa7ecb 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -125,6 +125,7 @@ bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" boku.base_url = "https://$-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 0402ae06459..288520722c3 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -65,6 +65,7 @@ pub enum RoutableConnectors { Boku, Braintree, Cashtocode, + // Chargebee, Checkout, Coinbase, Cryptopay, @@ -199,6 +200,7 @@ pub enum Connector { Boku, Braintree, Cashtocode, + // Chargebee, Checkout, Coinbase, Cryptopay, @@ -350,6 +352,7 @@ impl Connector { | Self::Boku | Self::Braintree | Self::Cashtocode + // | Self::Chargebee | Self::Coinbase | Self::Cryptopay | Self::Deutschebank diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index bd646c95eb9..15345c8221f 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -8,6 +8,7 @@ pub mod bitpay; pub mod bluesnap; pub mod boku; pub mod cashtocode; +pub mod chargebee; pub mod coinbase; pub mod cryptopay; pub mod ctp_mastercard; @@ -59,16 +60,17 @@ pub mod zsl; pub use self::{ airwallex::Airwallex, amazonpay::Amazonpay, bambora::Bambora, bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, - boku::Boku, cashtocode::Cashtocode, coinbase::Coinbase, cryptopay::Cryptopay, - ctp_mastercard::CtpMastercard, cybersource::Cybersource, datatrans::Datatrans, - deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, - fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globepay::Globepay, - gocardless::Gocardless, helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, - multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, - novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, payu::Payu, placetopay::Placetopay, - powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, - redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, - tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, volt::Volt, + boku::Boku, cashtocode::Cashtocode, chargebee::Chargebee, coinbase::Coinbase, + cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, cybersource::Cybersource, + datatrans::Datatrans, deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, + elavon::Elavon, fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, + globepay::Globepay, gocardless::Gocardless, helcim::Helcim, inespay::Inespay, + jpmorgan::Jpmorgan, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, + nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, + payu::Payu, placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, + rapyd::Rapyd, razorpay::Razorpay, redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, + taxjar::Taxjar, thunes::Thunes, tsys::Tsys, + unified_authentication_service::UnifiedAuthenticationService, volt::Volt, wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee.rs b/crates/hyperswitch_connectors/src/connectors/chargebee.rs new file mode 100644 index 00000000000..40dd137c281 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/chargebee.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as chargebee; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Chargebee { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Chargebee { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Chargebee {} +impl api::PaymentSession for Chargebee {} +impl api::ConnectorAccessToken for Chargebee {} +impl api::MandateSetup for Chargebee {} +impl api::PaymentAuthorize for Chargebee {} +impl api::PaymentSync for Chargebee {} +impl api::PaymentCapture for Chargebee {} +impl api::PaymentVoid for Chargebee {} +impl api::Refund for Chargebee {} +impl api::RefundExecute for Chargebee {} +impl api::RefundSync for Chargebee {} +impl api::PaymentToken for Chargebee {} + +impl ConnectorIntegration + for Chargebee +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Chargebee +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Chargebee { + fn id(&self) -> &'static str { + "chargebee" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.chargebee.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = chargebee::ChargebeeAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: chargebee::ChargebeeErrorResponse = res + .response + .parse_struct("ChargebeeErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Chargebee { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Chargebee { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Chargebee {} + +impl ConnectorIntegration + for Chargebee +{ +} + +impl ConnectorIntegration for Chargebee { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = chargebee::ChargebeeRouterData::from((amount, req)); + let connector_req = chargebee::ChargebeePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: chargebee::ChargebeePaymentsResponse = res + .response + .parse_struct("Chargebee PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Chargebee { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: chargebee::ChargebeePaymentsResponse = res + .response + .parse_struct("chargebee PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Chargebee { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: chargebee::ChargebeePaymentsResponse = res + .response + .parse_struct("Chargebee PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Chargebee {} + +impl ConnectorIntegration for Chargebee { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = chargebee::ChargebeeRouterData::from((refund_amount, req)); + let connector_req = chargebee::ChargebeeRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: chargebee::RefundResponse = res + .response + .parse_struct("chargebee RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Chargebee { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: chargebee::RefundResponse = res + .response + .parse_struct("chargebee RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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] +impl webhooks::IncomingWebhook for Chargebee { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Chargebee {} diff --git a/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs new file mode 100644 index 00000000000..f825a17acba --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/chargebee/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct ChargebeeRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for ChargebeeRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct ChargebeePaymentsRequest { + amount: StringMinorUnit, + card: ChargebeeCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct ChargebeeCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&ChargebeeRouterData<&PaymentsAuthorizeRouterData>> for ChargebeePaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &ChargebeeRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = ChargebeeCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct ChargebeeAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for ChargebeeAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum ChargebeePaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: ChargebeePaymentStatus) -> Self { + match item { + ChargebeePaymentStatus::Succeeded => Self::Charged, + ChargebeePaymentStatus::Failed => Self::Failure, + ChargebeePaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct ChargebeePaymentsResponse { + status: ChargebeePaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct ChargebeeRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&ChargebeeRouterData<&RefundsRouterData>> for ChargebeeRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &ChargebeeRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct ChargebeeErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index a2db7ad7b6e..e70aff630e3 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -105,6 +105,7 @@ default_imp_for_authorize_session_token!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -178,6 +179,7 @@ default_imp_for_calculate_tax!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -251,6 +253,7 @@ default_imp_for_session_update!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -325,6 +328,7 @@ default_imp_for_post_session_tokens!( connectors::Boku, connectors::Billwerk, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -398,6 +402,7 @@ default_imp_for_complete_authorize!( connectors::Bitpay, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Datatrans, @@ -463,6 +468,7 @@ default_imp_for_incremental_authorization!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Datatrans, @@ -536,6 +542,7 @@ default_imp_for_create_customer!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -608,6 +615,7 @@ default_imp_for_connector_redirect_response!( connectors::Bamboraapac, connectors::Bankofamerica, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -675,6 +683,7 @@ default_imp_for_pre_processing_steps!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Datatrans, @@ -746,6 +755,7 @@ default_imp_for_post_processing_steps!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -821,6 +831,7 @@ default_imp_for_approve!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -896,6 +907,7 @@ default_imp_for_reject!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -971,6 +983,7 @@ default_imp_for_webhook_source_verification!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1047,6 +1060,7 @@ default_imp_for_accept_dispute!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1122,6 +1136,7 @@ default_imp_for_submit_evidence!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1197,6 +1212,7 @@ default_imp_for_defend_dispute!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1281,6 +1297,7 @@ default_imp_for_file_upload!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1349,6 +1366,7 @@ default_imp_for_payouts!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Cryptopay, connectors::Datatrans, connectors::Coinbase, @@ -1424,6 +1442,7 @@ default_imp_for_payouts_create!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1501,6 +1520,7 @@ default_imp_for_payouts_retrieve!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1578,6 +1598,7 @@ default_imp_for_payouts_eligibility!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1655,6 +1676,7 @@ default_imp_for_payouts_fulfill!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Datatrans, @@ -1731,6 +1753,7 @@ default_imp_for_payouts_cancel!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1808,6 +1831,7 @@ default_imp_for_payouts_quote!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1885,6 +1909,7 @@ default_imp_for_payouts_recipient!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -1962,6 +1987,7 @@ default_imp_for_payouts_recipient_account!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2039,6 +2065,7 @@ default_imp_for_frm_sale!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2116,6 +2143,7 @@ default_imp_for_frm_checkout!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2193,6 +2221,7 @@ default_imp_for_frm_transaction!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2270,6 +2299,7 @@ default_imp_for_frm_fulfillment!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2347,6 +2377,7 @@ default_imp_for_frm_record_return!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Cybersource, @@ -2421,6 +2452,7 @@ default_imp_for_revoking_mandates!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::Datatrans, @@ -2494,6 +2526,7 @@ default_imp_for_uas_pre_authentication!( connectors::Bitpay, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -2567,6 +2600,7 @@ default_imp_for_uas_post_authentication!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 485ff86d728..9f0b319bfa1 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -215,6 +215,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -291,6 +292,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -362,6 +364,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -439,6 +442,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -514,6 +518,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -589,6 +594,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -675,6 +681,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -753,6 +760,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -831,6 +839,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -909,6 +918,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -987,6 +997,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1065,6 +1076,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1143,6 +1155,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1221,6 +1234,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1299,6 +1313,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1375,6 +1390,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1453,6 +1469,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1531,6 +1548,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1609,6 +1627,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1687,6 +1706,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1765,6 +1785,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, @@ -1840,6 +1861,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Bluesnap, connectors::Boku, connectors::Cashtocode, + connectors::Chargebee, connectors::Coinbase, connectors::Cryptopay, connectors::CtpMastercard, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 4a3b99636dc..efd41aaa4ac 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -24,6 +24,7 @@ pub struct Connectors { pub boku: ConnectorParams, pub braintree: ConnectorParams, pub cashtocode: ConnectorParams, + pub chargebee: ConnectorParams, pub checkout: ConnectorParams, pub coinbase: ConnectorParams, pub cryptopay: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 0bd9190f11c..c926401107c 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -36,8 +36,8 @@ pub use hyperswitch_connectors::connectors::{ airwallex, airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, bambora, bambora::Bambora, bamboraapac, bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, bluesnap::Bluesnap, boku, boku::Boku, - cashtocode, cashtocode::Cashtocode, coinbase, coinbase::Coinbase, cryptopay, - cryptopay::Cryptopay, ctp_mastercard, ctp_mastercard::CtpMastercard, cybersource, + cashtocode, cashtocode::Cashtocode, chargebee::Chargebee, coinbase, coinbase::Coinbase, + cryptopay, cryptopay::Cryptopay, ctp_mastercard, ctp_mastercard::CtpMastercard, cybersource, cybersource::Cybersource, datatrans, datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 28febe8b0b6..03b1ad0340b 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1041,6 +1041,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Cryptopay, connector::Coinbase, @@ -1578,6 +1579,7 @@ default_imp_for_new_connector_integration_frm!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Cryptopay, connector::Coinbase, @@ -1997,6 +1999,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Cryptopay, connector::Coinbase, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 9ce3ad6c020..459d8a50a37 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -431,6 +431,7 @@ default_imp_for_connector_request_id!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Coinbase, connector::Cryptopay, @@ -1521,6 +1522,7 @@ default_imp_for_fraud_check!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Cryptopay, connector::Cybersource, @@ -2110,6 +2112,7 @@ default_imp_for_connector_authentication!( connector::Boku, connector::Braintree, connector::Cashtocode, + connector::Chargebee, connector::Checkout, connector::Cryptopay, connector::Coinbase, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 243b6116e72..114a877d26f 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -359,6 +359,7 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(connector::Braintree::new()))) } enums::Connector::Cashtocode => { + // enums::Connector::Chargebee => Ok(ConnectorEnum::Old(Box::new(connector::Chargebee))), Ok(ConnectorEnum::Old(Box::new(connector::Cashtocode::new()))) } enums::Connector::Checkout => { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 06682f596ed..2348fae72ab 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -225,6 +225,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Boku => Self::Boku, api_enums::Connector::Braintree => Self::Braintree, api_enums::Connector::Cashtocode => Self::Cashtocode, + // api_enums::Connector::Chargebee => Self::Chargebee, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, api_enums::Connector::Cryptopay => Self::Cryptopay, diff --git a/crates/router/tests/connectors/chargebee.rs b/crates/router/tests/connectors/chargebee.rs new file mode 100644 index 00000000000..33d1c8f18de --- /dev/null +++ b/crates/router/tests/connectors/chargebee.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct ChargebeeTest; +impl ConnectorActions for ChargebeeTest {} +impl utils::Connector for ChargebeeTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Chargebee; + utils::construct_connector_data_old( + Box::new(Chargebee::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .chargebee + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "chargebee".to_string() + } +} + +static CONNECTOR: ChargebeeTest = ChargebeeTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 31ceb1a7245..19f0b9f9b2c 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -22,6 +22,7 @@ mod bitpay; mod bluesnap; mod boku; mod cashtocode; +mod chargebee; mod checkout; mod coinbase; mod cryptopay; diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 10db11bb3b0..44c178f3a3b 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -304,4 +304,7 @@ api_key="API Key" api_key="API Key" [unified_authentication_service] -api_key="API Key" \ No newline at end of file +api_key="API Key" + +[chargebee] +api_key= "API Key" \ No newline at end of file diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 7b6a1ba6b7a..3b02661c5ce 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -27,6 +27,7 @@ pub struct ConnectorAuthentication { pub bluesnap: Option, pub boku: Option, pub cashtocode: Option, + pub chargebee: Option, pub checkout: Option, pub coinbase: Option, pub cryptopay: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 61f5debb4d0..ac90dc88198 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -91,6 +91,7 @@ bluesnap.secondary_base_url = "https://sandpay.bluesnap.com/" boku.base_url = "https://country-api4-stage.boku.com" braintree.base_url = "https://payments.sandbox.braintree-api.com/graphql" cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" +chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 108e9d88263..32d24fe47eb 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From 26983acfee8006c338a5a53156ee0199ae6c53e4 Mon Sep 17 00:00:00 2001 From: manoj <34711289+manojradhakrishnan@users.noreply.github.com> Date: Wed, 29 Jan 2025 17:32:09 +0000 Subject: [PATCH 011/133] Documentation edits made through Mintlify web editor --- api-reference/mint.json | 410 ++++++++++++++++++++-------------------- 1 file changed, 208 insertions(+), 202 deletions(-) diff --git a/api-reference/mint.json b/api-reference/mint.json index 1130da2ffb0..a134b3c5b2b 100644 --- a/api-reference/mint.json +++ b/api-reference/mint.json @@ -15,10 +15,6 @@ } }, "tabs": [ - { - "name": "API Reference", - "url": "api-reference" - }, { "name": "Locker API Reference", "url": "locker-api-reference" @@ -38,212 +34,222 @@ ] }, { - "group": "Payments", - "pages": [ - "api-reference/payments/payments--create", - "api-reference/payments/payments--update", - "api-reference/payments/payments--confirm", - "api-reference/payments/payments--retrieve", - "api-reference/payments/payments--cancel", - "api-reference/payments/payments--capture", - "api-reference/payments/payments--incremental-authorization", - "api-reference/payments/payments--session-token", - "api-reference/payments/payments-link--retrieve", - "api-reference/payments/payments--list", - "api-reference/payments/payments--external-3ds-authentication", - "api-reference/payments/payments--complete-authorize" - ] - }, - { - "group": "Payment Methods", - "pages": [ - "api-reference/payment-methods/paymentmethods--create", - "api-reference/payment-methods/payment-method--retrieve", - "api-reference/payment-methods/payment-method--update", - "api-reference/payment-methods/payment-method--delete", - "api-reference/payment-methods/payment-method--set-default-payment-method-for-customer", - "api-reference/payment-methods/list-payment-methods-for-a-merchant", - "api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment", - "api-reference/payment-methods/list-payment-methods-for-a-customer", - "api-reference/customer-set-default-payment-method/customers--set-default-payment-method" - ] - }, - { - "group": "Customers", - "pages": [ - "api-reference/customers/customers--create", - "api-reference/customers/customers--retrieve", - "api-reference/customers/customers--update", - "api-reference/customers/customers--delete", - "api-reference/customers/customers--list" - ] - }, - { - "group": "Mandates", - "pages": [ - "api-reference/mandates/mandates--revoke-mandate", - "api-reference/mandates/mandates--retrieve-mandate", - "api-reference/mandates/mandates--customer-mandates-list" - ] - }, - { - "group": "Refunds", - "pages": [ - "api-reference/refunds/refunds--create", - "api-reference/refunds/refunds--update", - "api-reference/refunds/refunds--retrieve", - "api-reference/refunds/refunds--list" - ] - }, - { - "group": "Disputes", - "pages": [ - "api-reference/disputes/disputes--retrieve", - "api-reference/disputes/disputes--list" - ] - }, - { - "group": "Organization", - "pages": [ - "api-reference/organization/organization--create", - "api-reference/organization/organization--retrieve", - "api-reference/organization/organization--update" - ] - }, - { - "group": "Merchant Account", - "pages": [ - "api-reference/merchant-account/merchant-account--create", - "api-reference/merchant-account/merchant-account--retrieve", - "api-reference/merchant-account/merchant-account--update", - "api-reference/merchant-account/merchant-account--delete", - "api-reference/merchant-account/merchant-account--kv-status" - ] - }, - { - "group": "Business Profile", - "pages": [ - "api-reference/business-profile/business-profile--create", - "api-reference/business-profile/business-profile--update", - "api-reference/business-profile/business-profile--retrieve", - "api-reference/business-profile/business-profile--delete", - "api-reference/business-profile/business-profile--list" - ] - }, - { - "group": "API Key", + "group": "API Reference", "pages": [ - "api-reference/api-key/api-key--create", - "api-reference/api-key/api-key--retrieve", - "api-reference/api-key/api-key--update", - "api-reference/api-key/api-key--revoke", - "api-reference/api-key/api-key--list" + { + "group": "Payments", + "pages": [ + "api-reference/payments/payments--create", + "api-reference/payments/payments--update", + "api-reference/payments/payments--confirm", + "api-reference/payments/payments--retrieve", + "api-reference/payments/payments--cancel", + "api-reference/payments/payments--capture", + "api-reference/payments/payments--incremental-authorization", + "api-reference/payments/payments--session-token", + "api-reference/payments/payments-link--retrieve", + "api-reference/payments/payments--list", + "api-reference/payments/payments--external-3ds-authentication", + "api-reference/payments/payments--complete-authorize" + ] + }, + { + "group": "Payment Methods", + "pages": [ + "api-reference/payment-methods/paymentmethods--create", + "api-reference/payment-methods/payment-method--retrieve", + "api-reference/payment-methods/payment-method--update", + "api-reference/payment-methods/payment-method--delete", + "api-reference/payment-methods/payment-method--set-default-payment-method-for-customer", + "api-reference/payment-methods/list-payment-methods-for-a-merchant", + "api-reference/payment-methods/list-customer-saved-payment-methods-for-a-payment", + "api-reference/payment-methods/list-payment-methods-for-a-customer", + "api-reference/customer-set-default-payment-method/customers--set-default-payment-method" + ] + }, + { + "group": "Customers", + "pages": [ + "api-reference/customers/customers--create", + "api-reference/customers/customers--retrieve", + "api-reference/customers/customers--update", + "api-reference/customers/customers--delete", + "api-reference/customers/customers--list" + ] + }, + { + "group": "Mandates", + "pages": [ + "api-reference/mandates/mandates--revoke-mandate", + "api-reference/mandates/mandates--retrieve-mandate", + "api-reference/mandates/mandates--customer-mandates-list" + ] + }, + { + "group": "Refunds", + "pages": [ + "api-reference/refunds/refunds--create", + "api-reference/refunds/refunds--update", + "api-reference/refunds/refunds--retrieve", + "api-reference/refunds/refunds--list" + ] + }, + { + "group": "Disputes", + "pages": [ + "api-reference/disputes/disputes--retrieve", + "api-reference/disputes/disputes--list" + ] + }, + { + "group": "Organization", + "pages": [ + "api-reference/organization/organization--create", + "api-reference/organization/organization--retrieve", + "api-reference/organization/organization--update" + ] + }, + { + "group": "Merchant Account", + "pages": [ + "api-reference/merchant-account/merchant-account--create", + "api-reference/merchant-account/merchant-account--retrieve", + "api-reference/merchant-account/merchant-account--update", + "api-reference/merchant-account/merchant-account--delete", + "api-reference/merchant-account/merchant-account--kv-status" + ] + }, + { + "group": "Business Profile", + "pages": [ + "api-reference/business-profile/business-profile--create", + "api-reference/business-profile/business-profile--update", + "api-reference/business-profile/business-profile--retrieve", + "api-reference/business-profile/business-profile--delete", + "api-reference/business-profile/business-profile--list" + ] + }, + { + "group": "API Key", + "pages": [ + "api-reference/api-key/api-key--create", + "api-reference/api-key/api-key--retrieve", + "api-reference/api-key/api-key--update", + "api-reference/api-key/api-key--revoke", + "api-reference/api-key/api-key--list" + ] + }, + { + "group": "Merchant Connector Account", + "pages": [ + "api-reference/merchant-connector-account/merchant-connector--create", + "api-reference/merchant-connector-account/merchant-connector--retrieve", + "api-reference/merchant-connector-account/merchant-connector--update", + "api-reference/merchant-connector-account/merchant-connector--delete", + "api-reference/merchant-connector-account/merchant-connector--list" + ] + }, + { + "group": "Payouts", + "pages": [ + "api-reference/payouts/payouts--create", + "api-reference/payouts/payouts--update", + "api-reference/payouts/payouts--cancel", + "api-reference/payouts/payouts--fulfill", + "api-reference/payouts/payouts--confirm", + "api-reference/payouts/payouts--retrieve", + "api-reference/payouts/payouts--list", + "api-reference/payouts/payouts--list-filters", + "api-reference/payouts/payouts--filter" + ] + }, + { + "group": "Event", + "pages": [ + "api-reference/event/events--list", + "api-reference/event/events--delivery-attempt-list", + "api-reference/event/events--manual-retry" + ] + }, + { + "group": "GSM (Global Status Mapping)", + "pages": [ + "api-reference/gsm/gsm--create", + "api-reference/gsm/gsm--get", + "api-reference/gsm/gsm--update", + "api-reference/gsm/gsm--delete" + ] + }, + { + "group": "Poll", + "pages": ["api-reference/poll/poll--retrieve-poll-status"] + }, + { + "group": "Blocklist", + "pages": [ + "api-reference/blocklist/get-blocklist", + "api-reference/blocklist/post-blocklist", + "api-reference/blocklist/delete-blocklist", + "api-reference/blocklist/post-blocklisttoggle" + ] + }, + { + "group": "Routing", + "pages": [ + "api-reference/routing/routing--list", + "api-reference/routing/routing--create", + "api-reference/routing/routing--retrieve-config", + "api-reference/routing/routing--deactivate", + "api-reference/routing/routing--retrieve-default-config", + "api-reference/routing/routing--update-default-config", + "api-reference/routing/routing--retrieve-default-for-profile", + "api-reference/routing/routing--update-default-for-profile", + "api-reference/routing/routing--retrieve", + "api-reference/routing/routing--activate-config" + ] + }, + { + "group": "Relay", + "pages": [ + "api-reference/relay/relay", + "api-reference/relay/relay--retrieve" + ] + }, + { + "group": "Schemas", + "pages": ["api-reference/schemas/outgoing--webhook"] + } ] }, - { - "group": "Merchant Connector Account", - "pages": [ - "api-reference/merchant-connector-account/merchant-connector--create", - "api-reference/merchant-connector-account/merchant-connector--retrieve", - "api-reference/merchant-connector-account/merchant-connector--update", - "api-reference/merchant-connector-account/merchant-connector--delete", - "api-reference/merchant-connector-account/merchant-connector--list" - ] - }, - { - "group": "Payouts", - "pages": [ - "api-reference/payouts/payouts--create", - "api-reference/payouts/payouts--update", - "api-reference/payouts/payouts--cancel", - "api-reference/payouts/payouts--fulfill", - "api-reference/payouts/payouts--confirm", - "api-reference/payouts/payouts--retrieve", - "api-reference/payouts/payouts--list", - "api-reference/payouts/payouts--list-filters", - "api-reference/payouts/payouts--filter" - ] - }, - { - "group": "Event", - "pages": [ - "api-reference/event/events--list", - "api-reference/event/events--delivery-attempt-list", - "api-reference/event/events--manual-retry" - ] - }, - { - "group": "GSM (Global Status Mapping)", - "pages": [ - "api-reference/gsm/gsm--create", - "api-reference/gsm/gsm--get", - "api-reference/gsm/gsm--update", - "api-reference/gsm/gsm--delete" - ] - }, - { - "group": "Poll", - "pages": ["api-reference/poll/poll--retrieve-poll-status"] - }, { "group": "Hyperswitch Card Vault", "pages": ["locker-api-reference/overview"] }, { - "group": "Locker - Health", - "pages": ["locker-api-reference/locker-health/get-health"] - }, - { - "group": "Locker - Key Custodian", - "pages": [ - "locker-api-reference/key-custodian/provide-key-1", - "locker-api-reference/key-custodian/provide-key-2", - "locker-api-reference/key-custodian/unlock-the-locker" - ] - }, - { - "group": "Locker - Cards", + "group": "API Reference", "pages": [ - "locker-api-reference/cards/add-data-in-locker", - "locker-api-reference/cards/delete-data-from-locker", - "locker-api-reference/cards/retrieve-data-from-locker", - "locker-api-reference/cards/get-or-insert-the-card-fingerprint" + { + "group": "Locker - Health", + "pages": ["locker-api-reference/locker-health/get-health"] + }, + { + "group": "Locker - Key Custodian", + "pages": [ + "locker-api-reference/key-custodian/provide-key-1", + "locker-api-reference/key-custodian/provide-key-2", + "locker-api-reference/key-custodian/unlock-the-locker" + ] + }, + { + "group": "Locker - Cards", + "pages": [ + "locker-api-reference/cards/add-data-in-locker", + "locker-api-reference/cards/delete-data-from-locker", + "locker-api-reference/cards/retrieve-data-from-locker", + "locker-api-reference/cards/get-or-insert-the-card-fingerprint" + ] + } ] - }, - { - "group": "Blocklist", - "pages": [ - "api-reference/blocklist/get-blocklist", - "api-reference/blocklist/post-blocklist", - "api-reference/blocklist/delete-blocklist", - "api-reference/blocklist/post-blocklisttoggle" - ] - }, - { - "group": "Routing", - "pages": [ - "api-reference/routing/routing--list", - "api-reference/routing/routing--create", - "api-reference/routing/routing--retrieve-config", - "api-reference/routing/routing--deactivate", - "api-reference/routing/routing--retrieve-default-config", - "api-reference/routing/routing--update-default-config", - "api-reference/routing/routing--retrieve-default-for-profile", - "api-reference/routing/routing--update-default-for-profile", - "api-reference/routing/routing--retrieve", - "api-reference/routing/routing--activate-config" - ] - }, - { - "group": "Relay", - "pages": [ - "api-reference/relay/relay", - "api-reference/relay/relay--retrieve" - ] - }, - { - "group": "Schemas", - "pages": ["api-reference/schemas/outgoing--webhook"] } ], "footerSocials": { @@ -256,12 +262,12 @@ }, "analytics": { "gtm": { - "tagId": "GTM-PLBNKQFQ" + "tagId": "GTM-PLBNKQFQ" } }, "analytics": { "mixpanel": { - "projectToken": "b00355f29d9548d1333608df71d5d53d" + "projectToken": "b00355f29d9548d1333608df71d5d53d" } } } From c3333894ce64b1bc694f8c1eb09a8744aa850443 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 30 Jan 2025 00:29:54 +0000 Subject: [PATCH 012/133] chore(version): 2025.01.30.0 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bcd929bcbf..e571e3eac1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.01.30.0 + +### Features + +- **connector:** Add template code for chargebee ([#7036](https://github.com/juspay/hyperswitch/pull/7036)) ([`ad5491f`](https://github.com/juspay/hyperswitch/commit/ad5491f15bd8f61b2a918f584fe85132986176ad)) +- **router:** Add accept-language from request headers into browser-info ([#7074](https://github.com/juspay/hyperswitch/pull/7074)) ([`5381eb9`](https://github.com/juspay/hyperswitch/commit/5381eb992228164b552260c7ebb8a4cdbc1b3cb3)) + +### Refactors + +- **euclid:** Update proto file for elimination routing ([#7032](https://github.com/juspay/hyperswitch/pull/7032)) ([`275958a`](https://github.com/juspay/hyperswitch/commit/275958af14d0eb4385c995308fbf958c6b620e4f)) + +### Miscellaneous Tasks + +- Run clippy with default number of jobs in github workflows ([#7088](https://github.com/juspay/hyperswitch/pull/7088)) ([`337095b`](https://github.com/juspay/hyperswitch/commit/337095bce8c57be9a9a2ff8356ca9b70917b9851)) + +**Full Changelog:** [`2025.01.29.0...2025.01.30.0`](https://github.com/juspay/hyperswitch/compare/2025.01.29.0...2025.01.30.0) + +- - - + ## 2025.01.29.0 ### Bug Fixes From 05c81a704cbbd7c821833787dd924421e3ffcf85 Mon Sep 17 00:00:00 2001 From: likhinbopanna <131246334+likhinbopanna@users.noreply.github.com> Date: Thu, 30 Jan 2025 23:19:52 +0530 Subject: [PATCH 013/133] ci(cypress): log `x-request-id` for failed requests (#7118) --- .github/data/cards_info.csv | 1 + .../cypress/e2e/configs/Payment/Stripe.js | 12 +- cypress-tests/cypress/support/commands.js | 2821 +++++++++-------- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Update/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../Payments - Confirm/request.json | 2 +- .../request.json | 2 +- .../request.json | 2 +- .../request.json | 2 +- .../request.json | 2 +- .../Payments - cvv check fails/request.json | 4 +- .../request.json | 2 +- .../request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../QuickStart/Payments - Create/request.json | 2 +- .../request.json | 2 +- .../request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../PaymentMethods - Create/request.json | 2 +- .../Payments - Create/request.json | 2 +- .../Payments - Create Again/request.json | 2 +- .../Payments - Create Yet Again/request.json | 2 +- .../Payments/Payments - Create/request.json | 2 +- .../Payments/Payments - Update/request.json | 2 +- .../Payments - Create-copy/request.json | 2 +- .../QuickStart/Payments - Create/request.json | 2 +- .../Refunds/Payments - Create/request.json | 2 +- 48 files changed, 1559 insertions(+), 1367 deletions(-) diff --git a/.github/data/cards_info.csv b/.github/data/cards_info.csv index 4e7a19e4b23..f358214cc90 100644 --- a/.github/data/cards_info.csv +++ b/.github/data/cards_info.csv @@ -11,3 +11,4 @@ card_iin,card_issuer,card_network,card_type,card_subtype,card_issuing_country,ba 491761,BANKPOLSKAKASAOPIEKIS.A.(BANKPEKAOSA),Visa,CREDIT,BUSINESS,POLAND,,,,2015-09-07 12:58:50,, 510510,BANKOFHAWAII,Mastercard,CREDIT,,UNITEDSTATES,,,,2015-07-30 05:18:28,, 520474,MASTERCARD INTERNATIONAL,Visa,DEBIT,,UNITEDSTATES,,,840,2016-05-12 18:51:16,2022-12-12 15:17:33,Visa +378282,AmericanExpress,AmericanExpress,CREDIT,SMALLCORPORATE,INDIA,107,JP_AMEX,,2015-07-25 06:22:06,2021-02-23 07:37:55, \ No newline at end of file diff --git a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js index 1bfc1bff97c..d4d3c30fe27 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js @@ -5,7 +5,7 @@ import { import { getCustomExchange } from "./Modifiers"; const successfulNo3DSCardDetails = { - card_number: "4242424242424242", + card_number: "378282246310005", card_exp_month: "10", card_exp_year: "50", card_holder_name: "morino", @@ -82,12 +82,12 @@ const payment_method_data_3ds = { const payment_method_data_no3ds = { card: { - last4: "4242", + last4: "0005", card_type: "CREDIT", - card_network: "Visa", - card_issuer: "STRIPE PAYMENTS UK LIMITED", - card_issuing_country: "UNITEDKINGDOM", - card_isin: "424242", + card_network: "AmericanExpress", + card_issuer: "AmericanExpress", + card_issuing_country: "INDIA", + card_isin: "378282", card_extended_bin: null, card_exp_month: "10", card_exp_year: "50", diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 9d01f924b50..60691899f7c 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -63,13 +63,15 @@ Cypress.Commands.add("healthCheck", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body).to.equal("health is good"); - } else { - throw new Error( - `Health Check failed with status: \`${response.status}\` and body: \`${response.body}\`` - ); - } + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body).to.equal("health is good"); + } else { + throw new Error( + `Health Check failed with status: \`${response.status}\` and body: \`${response.body}\`` + ); + } + }); }); }); @@ -92,10 +94,12 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - // Handle the response as needed - globalState.set("profileId", response.body.default_profile); - globalState.set("publishableKey", response.body.publishable_key); - globalState.set("merchantDetails", response.body.merchant_details); + cy.wrap(response).then(() => { + // Handle the response as needed + globalState.set("profileId", response.body.default_profile); + globalState.set("publishableKey", response.body.publishable_key); + globalState.set("merchantDetails", response.body.merchant_details); + }); }); } ); @@ -114,17 +118,19 @@ Cypress.Commands.add("merchantRetrieveCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.payment_response_hash_key).to.not.be.empty; - expect(response.body.publishable_key).to.not.be.empty; - expect(response.body.default_profile).to.not.be.empty; - expect(response.body.organization_id).to.not.be.empty; - globalState.set("organizationId", response.body.organization_id); - - if (globalState.get("publishableKey") === undefined) { - globalState.set("publishableKey", response.body.publishable_key); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.payment_response_hash_key).to.not.be.empty; + expect(response.body.publishable_key).to.not.be.empty; + expect(response.body.default_profile).to.not.be.empty; + expect(response.body.organization_id).to.not.be.empty; + globalState.set("organizationId", response.body.organization_id); + + if (globalState.get("publishableKey") === undefined) { + globalState.set("publishableKey", response.body.publishable_key); + } + }); }); }); @@ -141,8 +147,10 @@ Cypress.Commands.add("merchantDeleteCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.deleted).to.equal(true); + cy.wrap(response).then(() => { + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.deleted).to.equal(true); + }); }); }); @@ -159,12 +167,15 @@ Cypress.Commands.add("ListConnectorsFeatureMatrixCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body).to.have.property("connectors").and.not.empty; - expect(response.body.connectors).to.be.an("array").and.not.empty; - response.body.connectors.forEach((item) => { - expect(item).to.have.property("description").and.not.empty; - expect(item).to.have.property("category").and.not.empty; - expect(item).to.have.property("supported_payment_methods").and.not.empty; + cy.wrap(response).then(() => { + expect(response.body).to.have.property("connectors").and.not.empty; + expect(response.body.connectors).to.be.an("array").and.not.empty; + response.body.connectors.forEach((item) => { + expect(item).to.have.property("description").and.not.empty; + expect(item).to.have.property("category").and.not.empty; + expect(item).to.have.property("supported_payment_methods").and.not + .empty; + }); }); }); }); @@ -182,14 +193,18 @@ Cypress.Commands.add("merchantListCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - for (const key in response.body) { - expect(response.body[key]).to.have.property("merchant_id").and.not.empty; - expect(response.body[key]).to.have.property("organization_id").and.not - .empty; - expect(response.body[key]).to.have.property("default_profile").and.not - .empty; - } + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + for (const key in response.body) { + expect(response.body[key]).to.have.property("merchant_id").and.not + .empty; + expect(response.body[key]).to.have.property("organization_id").and.not + .empty; + expect(response.body[key]).to.have.property("default_profile").and.not + .empty; + } + }); }); }); @@ -214,11 +229,14 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.publishable_key).to.equal(publishable_key); - expect(response.body.organization_id).to.equal(organization_id); - expect(response.body.merchant_details).to.not.equal(merchant_details); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.publishable_key).to.equal(publishable_key); + expect(response.body.organization_id).to.equal(organization_id); + expect(response.body.merchant_details).to.not.equal(merchant_details); + }); }); } ); @@ -248,15 +266,16 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - globalState.set(`${profilePrefix}Id`, response.body.profile_id); - - if (response.status === 200) { - expect(response.body.profile_id).to.not.to.be.null; - } else { - throw new Error( - `Business Profile call failed ${response.body.error.message}` - ); - } + cy.wrap(response).then(() => { + globalState.set(`${profilePrefix}Id`, response.body.profile_id); + if (response.status === 200) { + expect(response.body.profile_id).to.not.to.be.null; + } else { + throw new Error( + `Business Profile call failed ${response.body.error.message}` + ); + } + }); }); } ); @@ -300,24 +319,27 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - globalState.set( - "collectBillingDetails", - response.body.collect_billing_details_from_wallet_connector - ); - globalState.set( - "collectShippingDetails", - response.body.collect_shipping_details_from_wallet_connector - ); - globalState.set( - "alwaysCollectBillingDetails", - response.body.always_collect_billing_details_from_wallet_connector - ); - globalState.set( - "alwaysCollectShippingDetails", - response.body.always_collect_shipping_details_from_wallet_connector - ); - } + + cy.wrap(response).then(() => { + if (response.status === 200) { + globalState.set( + "collectBillingDetails", + response.body.collect_billing_details_from_wallet_connector + ); + globalState.set( + "collectShippingDetails", + response.body.collect_shipping_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectBillingDetails", + response.body.always_collect_billing_details_from_wallet_connector + ); + globalState.set( + "alwaysCollectShippingDetails", + response.body.always_collect_shipping_details_from_wallet_connector + ); + } + }); }); } ); @@ -351,24 +373,28 @@ Cypress.Commands.add("apiKeyCreateTest", (apiKeyCreateBody, globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body.merchant_id).to.equal(merchantId); - expect(response.body.description).to.equal(apiKeyCreateBody.description); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body.merchant_id).to.equal(merchantId); + expect(response.body.description).to.equal( + apiKeyCreateBody.description + ); - // API Key assertions are intentionally excluded to avoid being exposed in the logs - expect(response.body).to.have.property(keyIdType).and.to.include(keyId) - .and.to.not.be.empty; + // API Key assertions are intentionally excluded to avoid being exposed in the logs + expect(response.body).to.have.property(keyIdType).and.to.include(keyId) + .and.to.not.be.empty; - globalState.set("apiKeyId", response.body.key_id); - globalState.set("apiKey", response.body.api_key); + globalState.set("apiKeyId", response.body.key_id); + globalState.set("apiKey", response.body.api_key); - cy.task("setGlobalState", globalState.data); - } else { - // to be updated - throw new Error( - `API Key create call failed with status ${response.status} and message: "${response.body.error.message}"` - ); - } + cy.task("setGlobalState", globalState.data); + } else { + // to be updated + throw new Error( + `API Key create call failed with status ${response.status} and message: "${response.body.error.message}"` + ); + } + }); }); }); @@ -395,18 +421,20 @@ Cypress.Commands.add("apiKeyUpdateCall", (apiKeyUpdateBody, globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body.name).to.equal("Updated API Key"); - expect(response.body.merchant_id).to.equal(merchantId); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body.name).to.equal("Updated API Key"); + expect(response.body.merchant_id).to.equal(merchantId); - // API Key assertions are intentionally excluded to avoid being exposed in the logs - expect(response.body.key_id).to.equal(apiKeyId); - } else { - // to be updated - throw new Error( - `API Key create call failed with status ${response.status} and message: "${response.body.error.message}"` - ); - } + // API Key assertions are intentionally excluded to avoid being exposed in the logs + expect(response.body.key_id).to.equal(apiKeyId); + } else { + // to be updated + throw new Error( + `API Key create call failed with status ${response.status} and message: "${response.body.error.message}"` + ); + } + }); }); }); @@ -426,10 +454,12 @@ Cypress.Commands.add("apiKeyRetrieveCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.name).to.equal("Updated API Key"); - expect(response.body.key_id).to.equal(api_key_id); - expect(response.body.merchant_id).to.equal(merchant_id); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.name).to.equal("Updated API Key"); + expect(response.body.key_id).to.equal(api_key_id); + expect(response.body.merchant_id).to.equal(merchant_id); + }); }); }); @@ -447,19 +477,21 @@ Cypress.Commands.add("apiKeyListCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.be.an("array").and.not.empty; - for (const key in response.body) { - expect(response.body[key]).to.have.property("name").and.not.empty; - if (base_url.includes("sandbox") || base_url.includes("integ")) { - expect(response.body[key]).to.have.property("key_id").include("snd_") - .and.not.empty; - } else if (base_url.includes("localhost")) { - expect(response.body[key]).to.have.property("key_id").include("dev_") - .and.not.empty; + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body).to.be.an("array").and.not.empty; + for (const key in response.body) { + expect(response.body[key]).to.have.property("name").and.not.empty; + if (base_url.includes("sandbox") || base_url.includes("integ")) { + expect(response.body[key]).to.have.property("key_id").include("snd_") + .and.not.empty; + } else if (base_url.includes("localhost")) { + expect(response.body[key]).to.have.property("key_id").include("dev_") + .and.not.empty; + } + expect(response.body[key].merchant_id).to.equal(merchant_id); } - expect(response.body[key].merchant_id).to.equal(merchant_id); - } + }); }); }); @@ -479,10 +511,12 @@ Cypress.Commands.add("apiKeyDeleteCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.key_id).to.equal(api_key_id); - expect(response.body.revoked).to.equal(true); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.key_id).to.equal(api_key_id); + expect(response.body.revoked).to.equal(true); + }); }); }); @@ -529,22 +563,24 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(connectorName).to.equal(response.body.connector_name); - globalState.set( - `${mcaPrefix}Id`, - response.body.merchant_connector_id - ); - } else { - cy.task( - "cli_log", - "response status -> " + JSON.stringify(response.status) - ); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(connectorName).to.equal(response.body.connector_name); + globalState.set( + `${mcaPrefix}Id`, + response.body.merchant_connector_id + ); + } else { + cy.task( + "cli_log", + "response status -> " + JSON.stringify(response.status) + ); - throw new Error( - `Connector Create Call Failed ${response.body.error.message}` - ); - } + throw new Error( + `Connector Create Call Failed ${response.body.error.message}` + ); + } + }); }); } ); @@ -614,24 +650,26 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(globalState.get("connectorId")).to.equal( - response.body.connector_name - ); - globalState.set( - `${mcaPrefix}Id`, - response.body.merchant_connector_id - ); - } else { - cy.task( - "cli_log", - "response status -> " + JSON.stringify(response.status) - ); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(globalState.get("connectorId")).to.equal( + response.body.connector_name + ); + globalState.set( + `${mcaPrefix}Id`, + response.body.merchant_connector_id + ); + } else { + cy.task( + "cli_log", + "response status -> " + JSON.stringify(response.status) + ); - throw new Error( - `Connector Create Call Failed ${response.body.error.message}` - ); - } + throw new Error( + `Connector Create Call Failed ${response.body.error.message}` + ); + } + }); }); } ); @@ -688,24 +726,26 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(globalState.get("connectorId")).to.equal( - response.body.connector_name - ); - globalState.set( - "merchantConnectorId", - response.body.merchant_connector_id - ); - } else { - cy.task( - "cli_log", - "response status -> " + JSON.stringify(response.status) - ); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(globalState.get("connectorId")).to.equal( + response.body.connector_name + ); + globalState.set( + "merchantConnectorId", + response.body.merchant_connector_id + ); + } else { + cy.task( + "cli_log", + "response status -> " + JSON.stringify(response.status) + ); - throw new Error( - `Connector Create Call Failed ${response.body.error.message}` - ); - } + throw new Error( + `Connector Create Call Failed ${response.body.error.message}` + ); + } + }); }); } ); @@ -729,9 +769,14 @@ Cypress.Commands.add("connectorRetrieveCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.connector_name).to.equal(connector_id); - expect(response.body.merchant_connector_id).to.equal(merchant_connector_id); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.connector_name).to.equal(connector_id); + expect(response.body.merchant_connector_id).to.equal( + merchant_connector_id + ); + }); }); }); @@ -750,9 +795,13 @@ Cypress.Commands.add("connectorDeleteCall", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.merchant_connector_id).to.equal(merchant_connector_id); - expect(response.body.deleted).to.equal(true); + cy.wrap(response).then(() => { + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.merchant_connector_id).to.equal( + merchant_connector_id + ); + expect(response.body.deleted).to.equal(true); + }); }); }); @@ -783,12 +832,15 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.connector_name).to.equal(connector_id); - expect(response.body.merchant_connector_id).to.equal( - merchant_connector_id - ); - expect(response.body.connector_label).to.equal(connectorLabel); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.connector_name).to.equal(connector_id); + expect(response.body.merchant_connector_id).to.equal( + merchant_connector_id + ); + expect(response.body.connector_label).to.equal(connectorLabel); + }); }); } ); @@ -807,12 +859,15 @@ Cypress.Commands.add("connectorListByMid", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.be.an("array").and.not.empty; - response.body.forEach((item) => { - expect(item).to.not.have.property("metadata"); - expect(item).to.not.have.property("additional_merchant_data"); - expect(item).to.not.have.property("connector_wallets_details"); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body).to.be.an("array").and.not.empty; + response.body.forEach((item) => { + expect(item).to.not.have.property("metadata"); + expect(item).to.not.have.property("additional_merchant_data"); + expect(item).to.not.have.property("connector_wallets_details"); + }); }); }); }); @@ -831,20 +886,23 @@ Cypress.Commands.add( }).then((response) => { globalState.set("customerId", response.body.customer_id); logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id, "customer_id").to.not.be.empty; - expect(customerCreateBody.email, "email").to.equal(response.body.email); - expect(customerCreateBody.name, "name").to.equal(response.body.name); - expect(customerCreateBody.phone, "phone").to.equal(response.body.phone); - expect(customerCreateBody.metadata, "metadata").to.deep.equal( - response.body.metadata - ); - expect(customerCreateBody.address, "address").to.deep.equal( - response.body.address - ); - expect( - customerCreateBody.phone_country_code, - "phone_country_code" - ).to.equal(response.body.phone_country_code); + + cy.wrap(response).then(() => { + expect(response.body.customer_id, "customer_id").to.not.be.empty; + expect(customerCreateBody.email, "email").to.equal(response.body.email); + expect(customerCreateBody.name, "name").to.equal(response.body.name); + expect(customerCreateBody.phone, "phone").to.equal(response.body.phone); + expect(customerCreateBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect(customerCreateBody.address, "address").to.deep.equal( + response.body.address + ); + expect( + customerCreateBody.phone_country_code, + "phone_country_code" + ).to.equal(response.body.phone_country_code); + }); }); } ); @@ -860,9 +918,12 @@ Cypress.Commands.add("customerListCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - for (const key in response.body) { - expect(response.body[key]).to.not.be.empty; - } + + cy.wrap(response).then(() => { + for (const key in response.body) { + expect(response.body[key]).to.not.be.empty; + } + }); }); }); @@ -879,7 +940,10 @@ Cypress.Commands.add("customerRetrieveCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id).to.equal(customer_id).and.not.be.empty; + + cy.wrap(response).then(() => { + expect(response.body.customer_id).to.equal(customer_id).and.not.be.empty; + }); }); }); @@ -899,7 +963,10 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id).to.equal(customer_id); + + cy.wrap(response).then(() => { + expect(response.body.customer_id).to.equal(customer_id); + }); }); } ); @@ -919,10 +986,13 @@ Cypress.Commands.add("ephemeralGenerateCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id).to.equal(customer_id); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.id).to.exist.and.not.be.empty; - expect(response.body.secret).to.exist.and.not.be.empty; + + cy.wrap(response).then(() => { + expect(response.body.customer_id).to.equal(customer_id); + expect(response.body.merchant_id).to.equal(merchant_id); + expect(response.body.id).to.exist.and.not.be.empty; + expect(response.body.secret).to.exist.and.not.be.empty; + }); }); }); @@ -939,10 +1009,13 @@ Cypress.Commands.add("customerDeleteCall", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.body.customer_id).to.equal(customer_id).and.not.be.empty; - expect(response.body.customer_deleted).to.equal(true); - expect(response.body.address_deleted).to.equal(true); - expect(response.body.payment_methods_deleted).to.equal(true); + + cy.wrap(response).then(() => { + expect(response.body.customer_id).to.equal(customer_id).and.not.be.empty; + expect(response.body.customer_deleted).to.equal(true); + expect(response.body.address_deleted).to.equal(true); + expect(response.body.payment_methods_deleted).to.equal(true); + }); }); }); @@ -960,24 +1033,27 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body).to.have.property("currency"); - if (resData["payment_methods"].length == 1) { - function getPaymentMethodType(obj) { - return obj["payment_methods"][0]["payment_method_types"][0][ - "payment_method_type" - ]; + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body).to.have.property("currency"); + if (resData["payment_methods"].length == 1) { + function getPaymentMethodType(obj) { + return obj["payment_methods"][0]["payment_method_types"][0][ + "payment_method_type" + ]; + } + expect(getPaymentMethodType(resData)).to.equal( + getPaymentMethodType(response.body) + ); + } else { + expect(0).to.equal(response.body["payment_methods"].length); } - expect(getPaymentMethodType(resData)).to.equal( - getPaymentMethodType(response.body) - ); } else { - expect(0).to.equal(response.body["payment_methods"].length); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1001,41 +1077,43 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - const responsePaymentMethods = response.body["payment_methods"]; - const responseRequiredFields = - responsePaymentMethods[0]["payment_method_types"][0][ - "required_fields" - ]; - - const expectedRequiredFields = - data["payment_methods"][0]["payment_method_types"][0][ - "required_fields" - ]; - - Object.keys(expectedRequiredFields).forEach((key) => { - const expectedField = expectedRequiredFields[key]; - const responseField = responseRequiredFields[key]; - - expect(responseField).to.exist; - expect(responseField.required_field).to.equal( - expectedField.required_field - ); - expect(responseField.display_name).to.equal( - expectedField.display_name - ); - expect(responseField.field_type).to.deep.equal( - expectedField.field_type + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + const responsePaymentMethods = response.body["payment_methods"]; + const responseRequiredFields = + responsePaymentMethods[0]["payment_method_types"][0][ + "required_fields" + ]; + + const expectedRequiredFields = + data["payment_methods"][0]["payment_method_types"][0][ + "required_fields" + ]; + + Object.keys(expectedRequiredFields).forEach((key) => { + const expectedField = expectedRequiredFields[key]; + const responseField = responseRequiredFields[key]; + + expect(responseField).to.exist; + expect(responseField.required_field).to.equal( + expectedField.required_field + ); + expect(responseField.display_name).to.equal( + expectedField.display_name + ); + expect(responseField.field_type).to.deep.equal( + expectedField.field_type + ); + expect(responseField.value).to.equal(expectedField.value); + }); + } else { + throw new Error( + `List payment methods failed with status code "${response.status}" and error message "${response.body.error.message}"` ); - expect(responseField.value).to.equal(expectedField.value); - }); - } else { - throw new Error( - `List payment methods failed with status code "${response.status}" and error message "${response.body.error.message}"` - ); - } + } + }); }); } ); @@ -1054,32 +1132,35 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body).to.have.property("currency"); - if (resData["payment_methods"].length > 0) { - function getPaymentMethodType(obj) { - return obj["payment_methods"][0]["payment_method_types"][0][ - "card_networks" - ][0]["eligible_connectors"] - .slice() - .sort(); - } - const config_payment_method_type = getPaymentMethodType(resData); - const response_payment_method_type = getPaymentMethodType( - response.body - ); - for (let i = 0; i < response_payment_method_type.length; i++) { - expect(config_payment_method_type[i]).to.equal( - response_payment_method_type[i] + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body).to.have.property("currency"); + if (resData["payment_methods"].length > 0) { + function getPaymentMethodType(obj) { + return obj["payment_methods"][0]["payment_method_types"][0][ + "card_networks" + ][0]["eligible_connectors"] + .slice() + .sort(); + } + const config_payment_method_type = getPaymentMethodType(resData); + const response_payment_method_type = getPaymentMethodType( + response.body ); + for (let i = 0; i < response_payment_method_type.length; i++) { + expect(config_payment_method_type[i]).to.equal( + response_payment_method_type[i] + ); + } + } else { + expect(0).to.equal(response.body["payment_methods"].length); } } else { - expect(0).to.equal(response.body["payment_methods"].length); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1107,30 +1188,32 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - const expectedTokens = resData.body.session_token; - const actualTokens = response.body.session_token; + cy.wrap(response).then(() => { + if (response.status === 200) { + const expectedTokens = resData.body.session_token; + const actualTokens = response.body.session_token; - // Verifying length of array - expect(actualTokens.length, "arrayLength").to.equal( - expectedTokens.length - ); + // Verifying length of array + expect(actualTokens.length, "arrayLength").to.equal( + expectedTokens.length + ); - // Verify specific fields in each session_token object - expectedTokens.forEach((expectedToken, index) => { - const actualToken = actualTokens[index]; + // Verify specific fields in each session_token object + expectedTokens.forEach((expectedToken, index) => { + const actualToken = actualTokens[index]; - // Check specific fields only - expect(actualToken.wallet_name, "wallet_name").to.equal( - expectedToken.wallet_name - ); - expect(actualToken.connector, "connector").to.equal( - expectedToken.connector - ); - }); - } else { - defaultErrorHandler(response, resData); - } + // Check specific fields only + expect(actualToken.wallet_name, "wallet_name").to.equal( + expectedToken.wallet_name + ); + expect(actualToken.connector, "connector").to.equal( + expectedToken.connector + ); + }); + } else { + defaultErrorHandler(response, resData); + } + }); }); } ); @@ -1186,91 +1269,95 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - - if (resData.status === 200) { - expect(response.body).to.have.property("client_secret"); - const clientSecret = response.body.client_secret; - globalState.set("clientSecret", clientSecret); - globalState.set("paymentID", response.body.payment_id); - cy.log(clientSecret); - for (const key in resData.body) { - expect(resData.body[key]).to.equal( - response.body[key], - `Expected ${resData.body[key]} but got ${response.body[key]}` + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (resData.status === 200) { + expect(response.body).to.have.property("client_secret"); + const clientSecret = response.body.client_secret; + globalState.set("clientSecret", clientSecret); + globalState.set("paymentID", response.body.payment_id); + cy.log(clientSecret); + for (const key in resData.body) { + expect(resData.body[key]).to.equal( + response.body[key], + `Expected ${resData.body[key]} but got ${response.body[key]}` + ); + } + expect(response.body.payment_id, "payment_id").to.not.be.null; + expect(response.body.merchant_id, "merchant_id").to.not.be.null; + expect(createPaymentBody.amount, "amount").to.equal( + response.body.amount ); - } - expect(response.body.payment_id, "payment_id").to.not.be.null; - expect(response.body.merchant_id, "merchant_id").to.not.be.null; - expect(createPaymentBody.amount, "amount").to.equal( - response.body.amount - ); - expect(createPaymentBody.currency, "currency").to.equal( - response.body.currency - ); - expect(createPaymentBody.capture_method, "capture_method").to.equal( - response.body.capture_method - ); - expect( - createPaymentBody.authentication_type, - "authentication_type" - ).to.equal(response.body.authentication_type); - expect(createPaymentBody.description, "description").to.equal( - response.body.description - ); - expect(createPaymentBody.email, "email").to.equal(response.body.email); - expect(createPaymentBody.email, "customer.email").to.equal( - response.body.customer.email - ); - expect(createPaymentBody.customer_id, "customer.id").to.equal( - response.body.customer.id - ); - expect(createPaymentBody.metadata, "metadata").to.deep.equal( - response.body.metadata - ); - expect( - createPaymentBody.setup_future_usage, - "setup_future_usage" - ).to.equal(response.body.setup_future_usage); - // If 'shipping_cost' is not included in the request, the 'amount' in 'createPaymentBody' should match the 'amount_capturable' in the response. - if (typeof createPaymentBody?.shipping_cost === "undefined") { - expect(createPaymentBody.amount, "amount_capturable").to.equal( - response.body.amount_capturable + expect(createPaymentBody.currency, "currency").to.equal( + response.body.currency + ); + expect(createPaymentBody.capture_method, "capture_method").to.equal( + response.body.capture_method ); - } else { expect( - createPaymentBody.amount + createPaymentBody.shipping_cost, - "amount_capturable" - ).to.equal(response.body.amount_capturable); - } - expect(response.body.amount_received, "amount_received").to.be.oneOf([ - 0, - null, - ]); - expect(response.body.connector, "connector").to.be.null; - expect(createPaymentBody.capture_method, "capture_method").to.equal( - response.body.capture_method - ); - expect(response.body.payment_method, "payment_method").to.be.null; - expect(response.body.payment_method_data, "payment_method_data").to.be - .null; - expect(response.body.merchant_connector_id, "merchant_connector_id").to - .be.null; - expect(response.body.payment_method_id, "payment_method_id").to.be.null; - expect(response.body.payment_method_id, "payment_method_status").to.be - .null; - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect( - response.body.merchant_order_reference_id, - "merchant_order_reference_id" - ).to.be.null; - expect(response.body.connector_mandate_id, "connector_mandate_id").to.be - .null; + createPaymentBody.authentication_type, + "authentication_type" + ).to.equal(response.body.authentication_type); + expect(createPaymentBody.description, "description").to.equal( + response.body.description + ); + expect(createPaymentBody.email, "email").to.equal( + response.body.email + ); + expect(createPaymentBody.email, "customer.email").to.equal( + response.body.customer.email + ); + expect(createPaymentBody.customer_id, "customer.id").to.equal( + response.body.customer.id + ); + expect(createPaymentBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect( + createPaymentBody.setup_future_usage, + "setup_future_usage" + ).to.equal(response.body.setup_future_usage); + // If 'shipping_cost' is not included in the request, the 'amount' in 'createPaymentBody' should match the 'amount_capturable' in the response. + if (typeof createPaymentBody?.shipping_cost === "undefined") { + expect(createPaymentBody.amount, "amount_capturable").to.equal( + response.body.amount_capturable + ); + } else { + expect( + createPaymentBody.amount + createPaymentBody.shipping_cost, + "amount_capturable" + ).to.equal(response.body.amount_capturable); + } + expect(response.body.amount_received, "amount_received").to.be.oneOf([ + 0, + null, + ]); + expect(response.body.connector, "connector").to.be.null; + expect(createPaymentBody.capture_method, "capture_method").to.equal( + response.body.capture_method + ); + expect(response.body.payment_method, "payment_method").to.be.null; + expect(response.body.payment_method_data, "payment_method_data").to.be + .null; + expect(response.body.merchant_connector_id, "merchant_connector_id") + .to.be.null; + expect(response.body.payment_method_id, "payment_method_id").to.be + .null; + expect(response.body.payment_method_id, "payment_method_status").to.be + .null; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect( + response.body.merchant_order_reference_id, + "merchant_order_reference_id" + ).to.be.null; + expect(response.body.connector_mandate_id, "connector_mandate_id").to + .be.null; - validateErrorMessage(response, resData); - } else { - defaultErrorHandler(response, resData); - } + validateErrorMessage(response, resData); + } else { + defaultErrorHandler(response, resData); + } + }); }); } ); @@ -1289,38 +1376,40 @@ Cypress.Commands.add("paymentMethodsCallTest", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body).to.have.property("redirect_url"); - expect(response.body).to.have.property("payment_methods"); - if ( - globalState.get("collectBillingDetails") === true || - globalState.get("alwaysCollectBillingDetails") === true - ) { - expect( - response.body.collect_billing_details_from_wallets, - "collectBillingDetailsFromWallets" - ).to.be.true; - } else - expect( - response.body.collect_billing_details_from_wallets, - "collectBillingDetailsFromWallets" - ).to.be.false; + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body).to.have.property("redirect_url"); + expect(response.body).to.have.property("payment_methods"); + if ( + globalState.get("collectBillingDetails") === true || + globalState.get("alwaysCollectBillingDetails") === true + ) { + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_billing_details_from_wallets, + "collectBillingDetailsFromWallets" + ).to.be.false; - if ( - globalState.get("collectShippingDetails") === true || - globalState.get("alwaysCollectShippingDetails") === true - ) { - expect( - response.body.collect_shipping_details_from_wallets, - "collectShippingDetailsFromWallets" - ).to.be.true; - } else - expect( - response.body.collect_shipping_details_from_wallets, - "collectShippingDetailsFromWallets" - ).to.be.false; - globalState.set("paymentID", paymentIntentID); - cy.log(response); + if ( + globalState.get("collectShippingDetails") === true || + globalState.get("alwaysCollectShippingDetails") === true + ) { + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.true; + } else + expect( + response.body.collect_shipping_details_from_wallets, + "collectShippingDetailsFromWallets" + ).to.be.false; + globalState.set("paymentID", paymentIntentID); + cy.log(response); + }); }); }); @@ -1343,28 +1432,30 @@ Cypress.Commands.add("createPaymentMethodTest", (globalState, data) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body.client_secret, "client_secret").to.include( - "_secret_" - ).and.to.not.be.null; - expect(response.body.payment_method_id, "payment_method_id").to.not.be - .null; - expect(response.body.merchant_id, "merchant_id").to.equal(merchant_id); - expect(reqData.payment_method_type, "payment_method_type").to.equal( - response.body.payment_method_type - ); - expect(reqData.payment_method, "payment_method").to.equal( - response.body.payment_method - ); - expect(response.body.last_used_at, "last_used_at").to.not.be.null; - expect(reqData.customer_id, "customer_id").to.equal( - response.body.customer_id - ); - globalState.set("paymentMethodId", response.body.payment_method_id); - } else { - defaultErrorHandler(response, resData); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body.client_secret, "client_secret").to.include( + "_secret_" + ).and.to.not.be.null; + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; + expect(response.body.merchant_id, "merchant_id").to.equal(merchant_id); + expect(reqData.payment_method_type, "payment_method_type").to.equal( + response.body.payment_method_type + ); + expect(reqData.payment_method, "payment_method").to.equal( + response.body.payment_method + ); + expect(response.body.last_used_at, "last_used_at").to.not.be.null; + expect(reqData.customer_id, "customer_id").to.equal( + response.body.customer_id + ); + globalState.set("paymentMethodId", response.body.payment_method_id); + } else { + defaultErrorHandler(response, resData); + } + }); }); }); @@ -1384,20 +1475,22 @@ Cypress.Commands.add("deletePaymentMethodTest", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - - if (response.status === 200) { - expect(response.body.payment_method_id).to.equal(paymentMethodId); - expect(response.body.deleted).to.be.true; - } else if (response.status === 500 && baseUrl.includes("localhost")) { - // delete payment method api endpoint requires tartarus (hyperswitch card vault) to be set up since it makes a call to the locker service to delete the payment method - expect(response.body.error.code).to.include("HE_00"); - expect(response.body.error.message).to.include("Something went wrong"); - } else { - throw new Error( - `Payment Method Delete Call Failed with error message: ${response.body.error.message}` - ); - } + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body.payment_method_id).to.equal(paymentMethodId); + expect(response.body.deleted).to.be.true; + } else if (response.status === 500 && baseUrl.includes("localhost")) { + // delete payment method api endpoint requires tartarus (hyperswitch card vault) to be set up since it makes a call to the locker service to delete the payment method + expect(response.body.error.code).to.include("HE_00"); + expect(response.body.error.message).to.include("Something went wrong"); + } else { + throw new Error( + `Payment Method Delete Call Failed with error message: ${response.body.error.message}` + ); + } + }); }); }); @@ -1414,16 +1507,19 @@ Cypress.Commands.add("setDefaultPaymentMethodTest", (globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body).to.have.property( - "default_payment_method_id", - payment_method_id - ); - expect(response.body).to.have.property("customer_id", customer_id); - } else { - defaultErrorHandler(response); - } + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + expect(response.body).to.have.property( + "default_payment_method_id", + payment_method_id + ); + expect(response.body).to.have.property("customer_id", customer_id); + } else { + defaultErrorHandler(response); + } + }); }); }); @@ -1465,86 +1561,89 @@ Cypress.Commands.add( body: confirmBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", paymentIntentID); - globalState.set("connectorId", response.body.connector); - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(paymentIntentID, "payment_id").to.equal( - response.body.payment_id - ); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(merchantConnectorId, "connector_id").to.equal( - response.body.merchant_connector_id - ); - expect(response.body.customer, "customer").to.not.be.empty; - expect(response.body.billing, "billing_address").to.not.be.empty; - expect(response.body.profile_id, "profile_id").to.equal(profileId).and - .to.not.be.null; - - validateErrorMessage(response, resData); - - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentID", paymentIntentID); + globalState.set("connectorId", response.body.connector); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(paymentIntentID, "payment_id").to.equal( + response.body.payment_id + ); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(merchantConnectorId, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.equal(profileId).and + .to.not.be.null; + + validateErrorMessage(response, resData); + + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url ); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` ); } - } else { - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url ); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` ); } } else { throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + `Invalid capture method ${response.body.capture_method}` ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1581,78 +1680,83 @@ Cypress.Commands.add( body: confirmBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.headers["content-type"]).to.include("application/json"); - globalState.set("paymentID", paymentIntentId); - globalState.set("connectorId", response.body.connector); - globalState.set("paymentMethodType", confirmBody.payment_method_type); + cy.wrap(response).then(() => { if (response.status === 200) { - validateErrorMessage(response, resData); + expect(response.headers["content-type"]).to.include( + "application/json" + ); + globalState.set("paymentID", paymentIntentId); + globalState.set("connectorId", response.body.connector); + globalState.set("paymentMethodType", confirmBody.payment_method_type); - switch (response.body.authentication_type) { - case "three_ds": - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - if (response.body.status !== "failed") { - // we get many statuses here, hence this verification - if ( - connectorId === "adyen" && - response.body.payment_method_type === "blik" - ) { - expect(response.body) - .to.have.property("next_action") - .to.have.property("type") - .to.equal("wait_screen_information"); - } else { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url + if (response.status === 200) { + validateErrorMessage(response, resData); + + switch (response.body.authentication_type) { + case "three_ds": + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + if (response.body.status !== "failed") { + // we get many statuses here, hence this verification + if ( + connectorId === "adyen" && + response.body.payment_method_type === "blik" + ) { + expect(response.body) + .to.have.property("next_action") + .to.have.property("type") + .to.equal("wait_screen_information"); + } else { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url + ); + } + } else if (response.body.status === "failed") { + expect(response.body.error_code).to.equal( + resData.body.error_code ); } - } else if (response.body.status === "failed") { - expect(response.body.error_code).to.equal( - resData.body.error_code + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` ); } - } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); - } - break; - case "no_three_ds": - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - } else { + break; + case "no_three_ds": + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url + ); + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` + ); + } + break; + default: throw new Error( - `Invalid capture method ${response.body.capture_method}` + `Invalid authentication type ${response.body.authentication_type}` ); - } - break; - default: - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); + } } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1690,53 +1794,56 @@ Cypress.Commands.add( body: confirmBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", paymentIntentID); - validateErrorMessage(response, resData); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentID", paymentIntentID); + + validateErrorMessage(response, resData); - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - switch (response.body.payment_method_type) { - case "pix": - expect(response.body) - .to.have.property("next_action") - .to.have.property("qr_code_url"); - if (response.body.next_action.qr_code_url !== null) { - globalState.set( - "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, - response.body.next_action.qr_code_url - ); - globalState.set("nextActionType", "qr_code_url"); - } else { + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + switch (response.body.payment_method_type) { + case "pix": + expect(response.body) + .to.have.property("next_action") + .to.have.property("qr_code_url"); + if (response.body.next_action.qr_code_url !== null) { + globalState.set( + "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, + response.body.next_action.qr_code_url + ); + globalState.set("nextActionType", "qr_code_url"); + } else { + globalState.set( + "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, + response.body.next_action.image_data_url + ); + globalState.set("nextActionType", "image_data_url"); + } + break; + default: + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); globalState.set( - "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, - response.body.next_action.image_data_url + "nextActionUrl", + response.body.next_action.redirect_to_url ); - globalState.set("nextActionType", "image_data_url"); - } - break; - default: - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - break; + break; + } + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` + ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1774,39 +1881,42 @@ Cypress.Commands.add( body: confirmBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - validateErrorMessage(response, resData); - if ( - response.body.capture_method === "automatic" || - response.body.capture_method === "manual" - ) { - if (response.body.payment_method_type === "upi_collect") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - } else if (response.body.payment_method_type === "upi_intent") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("qr_code_fetch_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.qr_code_fetch_url + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + validateErrorMessage(response, resData); + + if ( + response.body.capture_method === "automatic" || + response.body.capture_method === "manual" + ) { + if (response.body.payment_method_type === "upi_collect") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url + ); + } else if (response.body.payment_method_type === "upi_intent") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("qr_code_fetch_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.qr_code_fetch_url + ); + } + } else { + throw new Error( + `Invalid capture method ${response.body.capture_method}` ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1852,85 +1962,86 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - globalState.set("clientSecret", response.body.client_secret); + cy.wrap(response).then(() => { + globalState.set("clientSecret", response.body.client_secret); + expect(response.headers["content-type"]).to.include("application/json"); - expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentAmount", createConfirmPaymentBody.amount); + globalState.set("paymentID", response.body.payment_id); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(response.body.payment_id, "payment_id").to.equal( + globalState.get("paymentID") + ); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(response.body.merchant_connector_id, "connector_id").to.equal( + merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body).to.have.property("status"); - if (response.status === 200) { - globalState.set("paymentAmount", createConfirmPaymentBody.amount); - globalState.set("paymentID", response.body.payment_id); - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(response.body.payment_id, "payment_id").to.equal( - globalState.get("paymentID") - ); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(response.body.merchant_connector_id, "connector_id").to.equal( - merchant_connector_id - ); - expect(response.body.customer, "customer").to.not.be.empty; - expect(response.body.billing, "billing_address").to.not.be.empty; - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body).to.have.property("status"); - - validateErrorMessage(response, resData); - - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + validateErrorMessage(response, resData); + + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url ); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else { + throw new Error( + `Invalid authentication type: ${response.body.authentication_type}` ); } - } else { - throw new Error( - `Invalid authentication type: ${response.body.authentication_type}` - ); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url ); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.deep.equal( - response.body[key] + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.deep.equal( + response.body[key] + ); + } + } else { + throw new Error( + `Invalid authentication type: ${response.body.authentication_type}` ); } - } else { - throw new Error( - `Invalid authentication type: ${response.body.authentication_type}` - ); } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -1974,77 +2085,79 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", paymentIntentID); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentID", paymentIntentID); - globalState.set("paymentID", paymentIntentID); - globalState.set("connectorId", response.body.connector); - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(paymentIntentID, "payment_id").to.equal( - response.body.payment_id - ); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(merchant_connector_id, "connector_id").to.equal( - response.body.merchant_connector_id - ); - expect(response.body.customer, "customer").to.not.be.empty; - if (reqData.billing !== null) { - expect(response.body.billing, "billing_address").to.not.be.empty; - } - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body.payment_token, "payment_token").to.not.be.null; - - validateErrorMessage(response, resData); - - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } - expect(response.body.customer_id).to.equal( - globalState.get("customerId") - ); - } else { - // Handle other authentication types as needed - throw new Error( - `Invalid authentication type: ${response.body.authentication_type}` - ); + globalState.set("paymentID", paymentIntentID); + globalState.set("connectorId", response.body.connector); + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(paymentIntentID, "payment_id").to.equal( + response.body.payment_id + ); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + if (reqData.billing !== null) { + expect(response.body.billing, "billing_address").to.not.be.empty; } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.payment_token, "payment_token").to.not.be.null; + + validateErrorMessage(response, resData); + + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + expect(response.body.customer_id).to.equal( + globalState.get("customerId") + ); + } else { + // Handle other authentication types as needed + throw new Error( + `Invalid authentication type: ${response.body.authentication_type}` + ); + } + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + expect(response.body.customer_id).to.equal( + globalState.get("customerId") + ); + } else { + // Handle other authentication types as needed + throw new Error( + `Invalid authentication type: ${response.body.authentication_type}` + ); } - expect(response.body.customer_id).to.equal( - globalState.get("customerId") - ); } else { - // Handle other authentication types as needed + // Handle other capture methods as needed throw new Error( - `Invalid authentication type: ${response.body.authentication_type}` + `Invalid capture method: ${response.body.capture_method}` ); } } else { - // Handle other capture methods as needed - throw new Error( - `Invalid capture method: ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2073,15 +2186,17 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.body.capture_method !== undefined) { - expect(response.body.payment_id).to.equal(payment_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.capture_method !== undefined) { + expect(response.body.payment_id).to.equal(payment_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2107,14 +2222,16 @@ Cypress.Commands.add("voidCallTest", (requestBody, data, globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); }); @@ -2140,55 +2257,57 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.payment_id).to.equal(payment_id); - expect(response.body.amount).to.equal(globalState.get("paymentAmount")); - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body.billing, "billing_address").to.not.be.empty; - expect(response.body.customer, "customer").to.not.be.empty; - if ( - ["succeeded", "processing", "requires_customer_action"].includes( - response.body.status - ) - ) { - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(response.body.payment_method, "payment_method").to.not.be.null; - expect(response.body.merchant_connector_id, "connector_id").to.equal( - merchant_connector_id - ); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.payment_id).to.equal(payment_id); + expect(response.body.amount).to.equal(globalState.get("paymentAmount")); + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.customer, "customer").to.not.be.empty; + if ( + ["succeeded", "processing", "requires_customer_action"].includes( + response.body.status + ) + ) { + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(response.body.payment_method, "payment_method").to.not.be.null; + expect(response.body.merchant_connector_id, "connector_id").to.equal( + merchant_connector_id + ); + } - if (autoretries) { - expect(response.body).to.have.property("attempts"); - expect(response.body.attempts).to.be.an("array").and.not.empty; - expect(response.body.attempts.length).to.equal(attempt); - expect(response.body.attempts[0].attempt_id).to.include( - `${payment_id}_` - ); - for (const key in response.body.attempts) { - if ( - response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && - response.body.status === "succeeded" - ) { - expect(response.body.attempts[key].status).to.equal("charged"); - } else if ( - response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && - response.body.status === "requires_customer_action" - ) { - expect(response.body.attempts[key].status).to.equal( - "authentication_pending" - ); - } else { - expect(response.body.attempts[key].status).to.equal("failure"); + if (autoretries) { + expect(response.body).to.have.property("attempts"); + expect(response.body.attempts).to.be.an("array").and.not.empty; + expect(response.body.attempts.length).to.equal(attempt); + expect(response.body.attempts[0].attempt_id).to.include( + `${payment_id}_` + ); + for (const key in response.body.attempts) { + if ( + response.body.attempts[key].attempt_id === + `${payment_id}_${attempt}` && + response.body.status === "succeeded" + ) { + expect(response.body.attempts[key].status).to.equal("charged"); + } else if ( + response.body.attempts[key].attempt_id === + `${payment_id}_${attempt}` && + response.body.status === "requires_customer_action" + ) { + expect(response.body.attempts[key].status).to.equal( + "authentication_pending" + ); + } else { + expect(response.body.attempts[key].status).to.equal("failure"); + } } } - } + }); }); } ); @@ -2217,17 +2336,19 @@ Cypress.Commands.add( body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("refundId", response.body.refund_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("refundId", response.body.refund_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + expect(response.body.payment_id).to.equal(payment_id); + } else { + defaultErrorHandler(response, resData); } - expect(response.body.payment_id).to.equal(payment_id); - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2248,10 +2369,12 @@ Cypress.Commands.add("syncRefundCallTest", (data, globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + }); }); }); @@ -2301,88 +2424,91 @@ Cypress.Commands.add( body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", response.body.payment_id); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(merchant_connector_id, "connector_id").to.equal( - response.body.merchant_connector_id - ); - expect(response.body.customer, "customer").to.not.be.empty; - expect(response.body.profile_id, "profile_id").to.not.be.null; - if (response.body.status !== "failed") { - expect(response.body.payment_method_id, "payment_method_id").to.not.be - .null; - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentID", response.body.payment_id); - if (requestBody.mandate_data === null) { - expect(response.body).to.have.property("payment_method_id"); - globalState.set("paymentMethodId", response.body.payment_method_id); - } else { - expect(response.body).to.have.property("mandate_id"); - globalState.set("mandateId", response.body.mandate_id); - } + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + if (response.body.status !== "failed") { + expect(response.body.payment_method_id, "payment_method_id").to.not + .be.null; + } - if (response.body.capture_method === "automatic") { - expect(response.body).to.have.property("mandate_id"); - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } + if (requestBody.mandate_data === null) { + expect(response.body).to.have.property("payment_method_id"); + globalState.set("paymentMethodId", response.body.payment_method_id); } else { - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); + expect(response.body).to.have.property("mandate_id"); + globalState.set("mandateId", response.body.mandate_id); } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - globalState.set( - "nextActionUrl", - response.body.next_action.redirect_to_url - ); - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + + if (response.body.capture_method === "automatic") { + expect(response.body).to.have.property("mandate_id"); + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url + ); + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + globalState.set( + "nextActionUrl", + response.body.next_action.redirect_to_url + ); + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } } else { throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + `Invalid capture method ${response.body.capture_method}` ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2425,77 +2551,80 @@ Cypress.Commands.add( body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", response.body.payment_id); - expect(response.body.payment_method_data, "payment_method_data").to.not - .be.empty; - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") - ); - expect(merchant_connector_id, "connector_id").to.equal( - response.body.merchant_connector_id - ); - expect(response.body.customer, "customer").to.not.be.empty; - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body.payment_method_id, "payment_method_id").to.not.be - .null; - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("paymentID", response.body.payment_id); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(merchant_connector_id, "connector_id").to.equal( + response.body.merchant_connector_id + ); + expect(response.body.customer, "customer").to.not.be.empty; + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.payment_method_id, "payment_method_id").to.not.be + .null; + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } } else { throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + `Invalid capture method ${response.body.capture_method}` ); } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else { - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + } else if (response.status === 400) { + if (response.body.error.message === "Mandate Validation Failed") { + expect(response.body.error.code).to.equal("HE_03"); + expect(response.body.error.message).to.equal( + "Mandate Validation Failed" + ); + expect(response.body.error.reason).to.equal( + "request amount is greater than mandate amount" ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); - } - } else if (response.status === 400) { - if (response.body.error.message === "Mandate Validation Failed") { - expect(response.body.error.code).to.equal("HE_03"); - expect(response.body.error.message).to.equal( - "Mandate Validation Failed" - ); - expect(response.body.error.reason).to.equal( - "request amount is greater than mandate amount" - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2540,69 +2669,73 @@ Cypress.Commands.add( body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("paymentID", response.body.payment_id); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + globalState.set("paymentID", response.body.payment_id); + + expect( + response.body.payment_method_id, + "payment_method_id" + ).to.include("pm_").and.to.not.be.null; + expect( + response.body.connector_transaction_id, + "connector_transaction_id" + ).to.not.be.null; + expect( + response.body.payment_method_status, + "payment_method_status" + ).to.equal("active"); - expect(response.body.payment_method_id, "payment_method_id").to.include( - "pm_" - ).and.to.not.be.null; - expect( - response.body.connector_transaction_id, - "connector_transaction_id" - ).to.not.be.null; - expect( - response.body.payment_method_status, - "payment_method_status" - ).to.equal("active"); - - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else { - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } } else { throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + `Invalid capture method ${response.body.capture_method}` ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2643,57 +2776,61 @@ Cypress.Commands.add( }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.headers["content-type"]).to.include("application/json"); + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.headers["content-type"]).to.include( + "application/json" + ); - globalState.set("paymentID", response.body.payment_id); - - if (response.body.capture_method === "automatic") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); - } - } else { - throw new Error( - `Invalid authentication type ${response.body.authentication_type}` - ); - } - } else if (response.body.capture_method === "manual") { - if (response.body.authentication_type === "three_ds") { - expect(response.body) - .to.have.property("next_action") - .to.have.property("redirect_to_url"); - const nextActionUrl = response.body.next_action.redirect_to_url; - cy.log(nextActionUrl); - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + globalState.set("paymentID", response.body.payment_id); + + if (response.body.capture_method === "automatic") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } - } else if (response.body.authentication_type === "no_three_ds") { - for (const key in resData.body) { - expect(resData.body[key], [key]).to.equal(response.body[key]); + } else if (response.body.capture_method === "manual") { + if (response.body.authentication_type === "three_ds") { + expect(response.body) + .to.have.property("next_action") + .to.have.property("redirect_to_url"); + const nextActionUrl = response.body.next_action.redirect_to_url; + cy.log(nextActionUrl); + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else if (response.body.authentication_type === "no_three_ds") { + for (const key in resData.body) { + expect(resData.body[key], [key]).to.equal(response.body[key]); + } + } else { + throw new Error( + `Invalid authentication type ${response.body.authentication_type}` + ); } } else { throw new Error( - `Invalid authentication type ${response.body.authentication_type}` + `Invalid capture method ${response.body.capture_method}` ); } } else { - throw new Error( - `Invalid capture method ${response.body.capture_method}` - ); + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2710,14 +2847,15 @@ Cypress.Commands.add("listMandateCallTest", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - - let i = 0; - for (i in response.body) { - if (response.body[i].mandate_id === globalState.get("mandateId")) { - expect(response.body[i].status).to.equal("active"); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + let i = 0; + for (i in response.body) { + if (response.body[i].mandate_id === globalState.get("mandateId")) { + expect(response.body[i].status).to.equal("active"); + } } - } + }); }); }); @@ -2734,12 +2872,16 @@ Cypress.Commands.add("revokeMandateCallTest", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.body.status === 200) { - expect(response.body.status).to.equal("revoked"); - } else if (response.body.status === 400) { - expect(response.body.reason).to.equal("Mandate has already been revoked"); - } + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.status === 200) { + expect(response.body.status).to.equal("revoked"); + } else if (response.body.status === 400) { + expect(response.body.reason).to.equal( + "Mandate has already been revoked" + ); + } + }); }); }); @@ -2826,39 +2968,42 @@ Cypress.Commands.add("listCustomerPMCallTest", (globalState) => { }, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.body.customer_payment_methods[0]?.payment_token) { - const paymentToken = - response.body.customer_payment_methods[0].payment_token; - const paymentMethodId = - response.body.customer_payment_methods[0].payment_method_id; - globalState.set("paymentToken", paymentToken); // Set paymentToken in globalState - globalState.set("paymentMethodId", paymentMethodId); // Set paymentMethodId in globalState - } else { - // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test - expect(response.body) - .to.have.property("customer_payment_methods") - .to.be.an("array").and.empty; - } - expect(globalState.get("customerId"), "customer_id").to.equal( - response.body.customer_payment_methods[0].customer_id - ); - expect( - response.body.customer_payment_methods[0].payment_token, - "payment_token" - ).to.not.be.null; - expect( - response.body.customer_payment_methods[0].payment_method_id, - "payment_method_id" - ).to.not.be.null; - expect( - response.body.customer_payment_methods[0].payment_method, - "payment_method" - ).to.not.be.null; - expect( - response.body.customer_payment_methods[0].payment_method_type, - "payment_method_type" - ).to.not.be.null; + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.customer_payment_methods[0]?.payment_token) { + const paymentToken = + response.body.customer_payment_methods[0].payment_token; + const paymentMethodId = + response.body.customer_payment_methods[0].payment_method_id; + globalState.set("paymentToken", paymentToken); // Set paymentToken in globalState + globalState.set("paymentMethodId", paymentMethodId); // Set paymentMethodId in globalState + } else { + // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test + expect(response.body) + .to.have.property("customer_payment_methods") + .to.be.an("array").and.empty; + } + expect(globalState.get("customerId"), "customer_id").to.equal( + response.body.customer_payment_methods[0].customer_id + ); + expect( + response.body.customer_payment_methods[0].payment_token, + "payment_token" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method_id, + "payment_method_id" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method, + "payment_method" + ).to.not.be.null; + expect( + response.body.customer_payment_methods[0].payment_method_type, + "payment_method_type" + ).to.not.be.null; + }); }); }); @@ -2874,24 +3019,27 @@ Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { }, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.body.customer_payment_methods[0]?.payment_token) { - const paymentToken = - response.body.customer_payment_methods[0].payment_token; - const paymentMethodId = - response.body.customer_payment_methods[0].payment_method_id; - globalState.set("paymentToken", paymentToken); - globalState.set("paymentMethodId", paymentMethodId); - expect( - response.body.customer_payment_methods[0].payment_method_id, - "payment_method_id" - ).to.not.be.null; - } else { - // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test - expect(response.body) - .to.have.property("customer_payment_methods") - .to.be.an("array").and.empty; - } + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.customer_payment_methods[0]?.payment_token) { + const paymentToken = + response.body.customer_payment_methods[0].payment_token; + const paymentMethodId = + response.body.customer_payment_methods[0].payment_method_id; + globalState.set("paymentToken", paymentToken); + globalState.set("paymentMethodId", paymentMethodId); + expect( + response.body.customer_payment_methods[0].payment_method_id, + "payment_method_id" + ).to.not.be.null; + } else { + // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test + expect(response.body) + .to.have.property("customer_payment_methods") + .to.be.an("array").and.empty; + } + }); }); }); @@ -2906,8 +3054,11 @@ Cypress.Commands.add("listRefundCallTest", (requestBody, globalState) => { body: requestBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.data).to.be.an("array").and.not.empty; + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.data).to.be.an("array").and.not.empty; + }); }); }); @@ -2934,17 +3085,19 @@ Cypress.Commands.add( body: createConfirmPayoutBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("payoutAmount", createConfirmPayoutBody.amount); - globalState.set("payoutID", response.body.payout_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("payoutAmount", createConfirmPayoutBody.amount); + globalState.set("payoutID", response.body.payout_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -2973,17 +3126,20 @@ Cypress.Commands.add( body: createConfirmPayoutBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("payoutAmount", createConfirmPayoutBody.amount); - globalState.set("payoutID", response.body.payout_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + globalState.set("payoutAmount", createConfirmPayoutBody.amount); + globalState.set("payoutID", response.body.payout_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -3006,15 +3162,18 @@ Cypress.Commands.add( body: payoutFulfillBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -3038,15 +3197,18 @@ Cypress.Commands.add( body: payoutConfirmBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -3064,9 +3226,11 @@ Cypress.Commands.add("retrievePayoutCallTest", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.payout_id).to.equal(payout_id); - expect(response.body.amount).to.equal(globalState.get("payoutAmount")); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + expect(response.body.payout_id).to.equal(payout_id); + expect(response.body.amount).to.equal(globalState.get("payoutAmount")); + }); }); }); @@ -3092,22 +3256,24 @@ Cypress.Commands.add("userLogin", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - if (response.body.token_type === "totp") { - expect(response.body, "totp_token").to.have.property("token").and.to.not - .be.empty; + cy.wrap(response).then(() => { + if (response.status === 200) { + if (response.body.token_type === "totp") { + expect(response.body, "totp_token").to.have.property("token").and.to + .not.be.empty; - const totpToken = response.body.token; - if (!totpToken) { - throw new Error("No token received from login"); + const totpToken = response.body.token; + if (!totpToken) { + throw new Error("No token received from login"); + } + globalState.set("totpToken", totpToken); } - globalState.set("totpToken", totpToken); + } else { + throw new Error( + `User login call failed to get totp token with status: "${response.status}" and message: "${response.body.error.message}"` + ); } - } else { - throw new Error( - `User login call failed to get totp token with status: "${response.status}" and message: "${response.body.error.message}"` - ); - } + }); }); }); Cypress.Commands.add("terminate2Fa", (globalState) => { @@ -3128,22 +3294,24 @@ Cypress.Commands.add("terminate2Fa", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - if (response.body.token_type === "user_info") { - expect(response.body, "user_info_token").to.have.property("token").and - .to.not.be.empty; + cy.wrap(response).then(() => { + if (response.status === 200) { + if (response.body.token_type === "user_info") { + expect(response.body, "user_info_token").to.have.property("token").and + .to.not.be.empty; - const userInfoToken = response.body.token; - if (!userInfoToken) { - throw new Error("No user info token received"); + const userInfoToken = response.body.token; + if (!userInfoToken) { + throw new Error("No user info token received"); + } + globalState.set("userInfoToken", userInfoToken); } - globalState.set("userInfoToken", userInfoToken); + } else { + throw new Error( + `2FA terminate call failed with status: "${response.status}" and message: "${response.body.error.message}"` + ); } - } else { - throw new Error( - `2FA terminate call failed with status: "${response.status}" and message: "${response.body.error.message}"` - ); - } + }); }); }); Cypress.Commands.add("userInfo", (globalState) => { @@ -3167,24 +3335,26 @@ Cypress.Commands.add("userInfo", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body, "merchant_id").to.have.property("merchant_id").and - .to.not.be.empty; - expect(response.body, "organization_id").to.have.property("org_id").and.to - .not.be.empty; - expect(response.body, "profile_id").to.have.property("profile_id").and.to - .not.be.empty; + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body, "merchant_id").to.have.property("merchant_id").and + .to.not.be.empty; + expect(response.body, "organization_id").to.have.property("org_id").and + .to.not.be.empty; + expect(response.body, "profile_id").to.have.property("profile_id").and + .to.not.be.empty; - globalState.set("merchantId", response.body.merchant_id); - globalState.set("organizationId", response.body.org_id); - globalState.set("profileId", response.body.profile_id); + globalState.set("merchantId", response.body.merchant_id); + globalState.set("organizationId", response.body.org_id); + globalState.set("profileId", response.body.profile_id); - globalState.set("userInfoToken", userInfoToken); - } else { - throw new Error( - `User login call failed to fetch user info with status: "${response.status}" and message: "${response.body.error.message}"` - ); - } + globalState.set("userInfoToken", userInfoToken); + } else { + throw new Error( + `User login call failed to fetch user info with status: "${response.status}" and message: "${response.body.error.message}"` + ); + } + }); }); }); @@ -3203,11 +3373,13 @@ Cypress.Commands.add("ListMcaByMid", (globalState) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - globalState.set("profileId", response.body[0].profile_id); - globalState.set("stripeMcaId", response.body[0].merchant_connector_id); - globalState.set("adyenMcaId", response.body[1].merchant_connector_id); - globalState.set("bluesnapMcaId", response.body[3].merchant_connector_id); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + globalState.set("profileId", response.body[0].profile_id); + globalState.set("stripeMcaId", response.body[0].merchant_connector_id); + globalState.set("adyenMcaId", response.body[1].merchant_connector_id); + globalState.set("bluesnapMcaId", response.body[3].merchant_connector_id); + }); }); }); @@ -3235,17 +3407,20 @@ Cypress.Commands.add( body: routingBody, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body).to.have.property("id"); - globalState.set("routingConfigId", response.body.id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + expect(response.body).to.have.property("id"); + globalState.set("routingConfigId", response.body.id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); } ); @@ -3264,16 +3439,19 @@ Cypress.Commands.add("activateRoutingConfig", (data, globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body.id).to.equal(routing_config_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + expect(response.body.id).to.equal(routing_config_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); }); @@ -3291,16 +3469,19 @@ Cypress.Commands.add("retrieveRoutingConfig", (data, globalState) => { failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - expect(response.body.id).to.equal(routing_config_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + + if (response.status === 200) { + expect(response.body.id).to.equal(routing_config_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); + } + } else { + defaultErrorHandler(response, resData); } - } else { - defaultErrorHandler(response, resData); - } + }); }); }); @@ -3319,15 +3500,20 @@ Cypress.Commands.add( failOnStatusCode: false, }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body) - .to.have.property("message") - .to.equal("card_declined"); - expect(response.body).to.have.property("connector").to.equal("stripe"); - expect(response.body) - .to.have.property("step_up_possible") - .to.equal(step_up_possible); - } + + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body) + .to.have.property("message") + .to.equal("card_declined"); + expect(response.body) + .to.have.property("connector") + .to.equal("stripe"); + expect(response.body) + .to.have.property("step_up_possible") + .to.equal(step_up_possible); + } + }); }); } ); @@ -3352,49 +3538,52 @@ Cypress.Commands.add("incrementalAuth", (globalState, data) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body.amount_capturable, "amount_capturable").to.equal( - resData.body.amount_capturable - ); - expect(response.body.authorization_count, "authorization_count").to.be.a( - "number" - ).and.not.be.null; - expect( - response.body.incremental_authorization_allowed, - "incremental_authorization_allowed" - ).to.be.true; - expect( - response.body.incremental_authorizations, - "incremental_authorizations" - ).to.be.an("array").and.not.be.empty; - expect(response.body.payment_id, "payment_id").to.equal(paymentId); - expect(response.body.status, "status").to.equal(resData.body.status); - - for (const key in response.body.incremental_authorizations) { - expect(response.body.incremental_authorizations[key], "amount") - .to.have.property("amount") - .to.be.a("number") - .to.equal(resData.body.amount).and.not.be.null; + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body.amount_capturable, "amount_capturable").to.equal( + resData.body.amount_capturable + ); expect( - response.body.incremental_authorizations[key], - "error_code" - ).to.have.property("error_code").to.be.null; + response.body.authorization_count, + "authorization_count" + ).to.be.a("number").and.not.be.null; expect( - response.body.incremental_authorizations[key], - "error_message" - ).to.have.property("error_message").to.be.null; + response.body.incremental_authorization_allowed, + "incremental_authorization_allowed" + ).to.be.true; expect( - response.body.incremental_authorizations[key], - "previously_authorized_amount" - ) - .to.have.property("previously_authorized_amount") - .to.be.a("number") - .to.equal(response.body.amount).and.not.be.null; - expect(response.body.incremental_authorizations[key], "status") - .to.have.property("status") - .to.equal("success"); + response.body.incremental_authorizations, + "incremental_authorizations" + ).to.be.an("array").and.not.be.empty; + expect(response.body.payment_id, "payment_id").to.equal(paymentId); + expect(response.body.status, "status").to.equal(resData.body.status); + + for (const key in response.body.incremental_authorizations) { + expect(response.body.incremental_authorizations[key], "amount") + .to.have.property("amount") + .to.be.a("number") + .to.equal(resData.body.amount).and.not.be.null; + expect( + response.body.incremental_authorizations[key], + "error_code" + ).to.have.property("error_code").to.be.null; + expect( + response.body.incremental_authorizations[key], + "error_message" + ).to.have.property("error_message").to.be.null; + expect( + response.body.incremental_authorizations[key], + "previously_authorized_amount" + ) + .to.have.property("previously_authorized_amount") + .to.be.a("number") + .to.equal(response.body.amount).and.not.be.null; + expect(response.body.incremental_authorizations[key], "status") + .to.have.property("status") + .to.equal("success"); + } } - } + }); }); }); @@ -3437,13 +3626,15 @@ Cypress.Commands.add("setConfigs", (globalState, key, value, requestType) => { }).then((response) => { logRequestId(response.headers["x-request-id"]); - if (response.status === 200) { - expect(response.body).to.have.property("key").to.equal(key); - expect(response.body).to.have.property("value").to.equal(value); - } else { - throw new Error( - `Failed to set configs with status ${response.status} and message ${response.body.error.message}` - ); - } + cy.wrap(response).then(() => { + if (response.status === 200) { + expect(response.body).to.have.property("key").to.equal(key); + expect(response.body).to.have.property("value").to.equal(value); + } else { + throw new Error( + `Failed to set configs with status ${response.status} and message ${response.body.error.message}` + ); + } + }); }); }); diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json index 48b7d410897..1cdee895b37 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Create/request.json @@ -47,7 +47,7 @@ "payment_method_type": "debit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "01", "card_exp_year": "26", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json index b32011d26dc..47912f2025e 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario10-Create a mandate and recurring payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json index 18f12b7306d..7dfbf639e51 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario11-Refund recurring payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create a payment and Confirm/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create a payment and Confirm/Payments - Confirm/request.json index 540a2fa1946..8becf0a7c19 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create a payment and Confirm/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2-Create a payment and Confirm/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "Joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update Amount/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update Amount/Payments - Confirm/request.json index 1b1757b9a49..e532d039fb1 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update Amount/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario22- Update Amount/Payments - Confirm/request.json @@ -49,7 +49,7 @@ "payment_method_type": "debit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "03", "card_exp_year": "2030", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario23-Add card flow/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario23-Add card flow/Payments - Create/request.json index 4f64a5d0e34..e895ace0e11 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario23-Add card flow/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario23-Add card flow/Payments - Create/request.json @@ -45,7 +45,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4111111111111111", + "card_number": "378282246310005", "card_exp_month": "03", "card_exp_year": "2030", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario26-Create payment without customer_id and with billing address and shipping address/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario26-Create payment without customer_id and with billing address and shipping address/Payments - Create/request.json index 627c816fba9..09ffe6396d3 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario26-Create payment without customer_id and with billing address and shipping address/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario26-Create payment without customer_id and with billing address and shipping address/Payments - Create/request.json @@ -47,7 +47,7 @@ "payment_method_type": "debit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "01", "card_exp_year": "26", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Confirm a payment with requires_customer_action status/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Confirm a payment with requires_customer_action status/Payments - Confirm/request.json index 1ce1d61eaa1..91acf28218e 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Confirm a payment with requires_customer_action status/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario27-Confirm a payment with requires_customer_action status/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "Joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario28-Create payment with payment method billing/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario28-Create payment with payment method billing/Payments - Create/request.json index 578d3c47267..77323059b83 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario28-Create payment with payment method billing/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario28-Create payment with payment method billing/Payments - Create/request.json @@ -39,7 +39,7 @@ "payment_method_type": "debit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "01", "card_exp_year": "26", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario29-Update payment with payment method billing/Payments - Update/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario29-Update payment with payment method billing/Payments - Update/request.json index a8fc9965c7d..eafa6b9d22e 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario29-Update payment with payment method billing/Payments - Update/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario29-Update payment with payment method billing/Payments - Update/request.json @@ -21,7 +21,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "01", "card_exp_year": "26", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json index 604ac54144d..9e1278c7d95 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2a-Create payment with confirm false card holder name null/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": null, diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json index 371ef616fe4..02de494d68c 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario2b-Create payment with confirm false card holder name empty/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json index 77f4d0dafe7..a0e82b8610d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario30-Pass payment method billing in Confirm/Payments - Confirm/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario30-Pass payment method billing in Confirm/Payments - Confirm/request.json index b92a83b5adc..2ded3043c3d 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario30-Pass payment method billing in Confirm/Payments - Confirm/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario30-Pass payment method billing in Confirm/Payments - Confirm/request.json @@ -41,7 +41,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "Joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Card CVC/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Card CVC/request.json index 77b40a8ece1..bfb1e3d863e 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Card CVC/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Card CVC/request.json @@ -23,7 +23,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry month/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry month/request.json index 2c3d0fe427c..6475ad1ef1a 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry month/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry month/request.json @@ -23,7 +23,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "13", "card_exp_year": "69", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry year/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry year/request.json index 63eda0afbe0..17a69bb2bfa 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry year/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Invalid Expiry year/request.json @@ -23,7 +23,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "22", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Success/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Success/request.json index 05bc9dbc321..baa616ddc75 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Success/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario31-Ensure API Contract for Payment Method Data/Payments - Card Payment Method Success/request.json @@ -23,7 +23,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - cvv check fails/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - cvv check fails/request.json index 9f5c6a8fb3a..ac816e25d30 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - cvv check fails/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - cvv check fails/request.json @@ -25,8 +25,8 @@ "payment_method_data": { "card": { "card_number": "4000000000000101", - "card_exp_month": "10", - "card_exp_year": "25", + "card_exp_month": "12", + "card_exp_year": "34", "card_holder_name": "joseph Doe", "card_cvc": "123" } diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - payment check populated for successful payment without billing/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - payment check populated for successful payment without billing/request.json index 88fccca9a0f..b6f0a69ef95 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - payment check populated for successful payment without billing/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - payment check populated for successful payment without billing/request.json @@ -24,7 +24,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - successful payment with billing/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - successful payment with billing/request.json index 28e45766722..2ebb0ebb813 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - successful payment with billing/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario32-Check is payment checks are populated/Payments - successful payment with billing/request.json @@ -24,7 +24,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json index 4deef11deb5..4a2876af98c 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json index f1ff4cb5692..978ad9bd542 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario4a-Create payment with manual_multiple capture/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario5-Void the payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario5-Void the payment/Payments - Create/request.json index 4deef11deb5..4a2876af98c 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario5-Void the payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario5-Void the payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9-Refund full payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9-Refund full payment/Payments - Create/request.json index f58c425653c..665946fccf9 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9-Refund full payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9-Refund full payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json index f58c425653c..665946fccf9 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Happy Cases/Scenario9a-Partial refund/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payments - Create/request.json index 2225c6096e3..322d38b08c6 100644 --- a/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/QuickStart/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json index 02e431466aa..3017be462df 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp Year)/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "2022", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json index 52c73bea743..851b8538df1 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario1-Create payment with Invalid card details/Payments - Create(Invalid Exp month)/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "01", "card_exp_year": "2023", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json index d6f53f92a04..c55925d85a5 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario10-Refund exceeds amount captured/Payments - Create/request.json @@ -34,7 +34,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json index 4deef11deb5..4a2876af98c 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json index f58c425653c..665946fccf9 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario4-Capture the succeeded payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json index f58c425653c..665946fccf9 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario5-Void the success_slash_failure payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Payments - Create/request.json index f58c425653c..665946fccf9 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario7-Refund exceeds amount/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json index 3e1f1231ff8..e52dcb06a50 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario8-Refund for unsuccessful payment/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario9-Create a recurring payment with greater mandate amount/Payments - Create/request.json b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario9-Create a recurring payment with greater mandate amount/Payments - Create/request.json index b32011d26dc..47912f2025e 100644 --- a/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario9-Create a recurring payment with greater mandate amount/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Flow Testcases/Variation Cases/Scenario9-Create a recurring payment with greater mandate amount/Payments - Create/request.json @@ -35,7 +35,7 @@ "payment_method": "card", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json b/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json index 2617b3e44a0..f3b7f262b8f 100644 --- a/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json +++ b/postman/collection-dir/stripe/PaymentMethods/PaymentMethods - Create/request.json @@ -22,7 +22,7 @@ "payment_method_type": "credit", "payment_method_issuer": "Visa", "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "John Doe" diff --git a/postman/collection-dir/stripe/PaymentMethods/Payments - Create/request.json b/postman/collection-dir/stripe/PaymentMethods/Payments - Create/request.json index 93bda640229..44e9559483d 100644 --- a/postman/collection-dir/stripe/PaymentMethods/Payments - Create/request.json +++ b/postman/collection-dir/stripe/PaymentMethods/Payments - Create/request.json @@ -45,7 +45,7 @@ "payment_method_type": "debit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Payments/Payments - Create Again/request.json b/postman/collection-dir/stripe/Payments/Payments - Create Again/request.json index c985bf50b0d..d0a12e7a4a0 100644 --- a/postman/collection-dir/stripe/Payments/Payments - Create Again/request.json +++ b/postman/collection-dir/stripe/Payments/Payments - Create Again/request.json @@ -36,7 +36,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Payments/Payments - Create Yet Again/request.json b/postman/collection-dir/stripe/Payments/Payments - Create Yet Again/request.json index 2b6d4bf3338..b7ecd39f2e7 100644 --- a/postman/collection-dir/stripe/Payments/Payments - Create Yet Again/request.json +++ b/postman/collection-dir/stripe/Payments/Payments - Create Yet Again/request.json @@ -36,7 +36,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Payments/Payments - Create/request.json b/postman/collection-dir/stripe/Payments/Payments - Create/request.json index 5a7457cedf9..a9b867f0f6f 100644 --- a/postman/collection-dir/stripe/Payments/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Payments/Payments - Create/request.json @@ -36,7 +36,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Payments/Payments - Update/request.json b/postman/collection-dir/stripe/Payments/Payments - Update/request.json index f3cdd29f872..5bc7fcdf2bc 100644 --- a/postman/collection-dir/stripe/Payments/Payments - Update/request.json +++ b/postman/collection-dir/stripe/Payments/Payments - Update/request.json @@ -34,7 +34,7 @@ "return_url": "https://duck.com", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json index 5fcc5647594..a27c309c9ab 100644 --- a/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create-copy/request.json @@ -36,7 +36,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/QuickStart/Payments - Create/request.json b/postman/collection-dir/stripe/QuickStart/Payments - Create/request.json index 13ed1a212de..24681fb62de 100644 --- a/postman/collection-dir/stripe/QuickStart/Payments - Create/request.json +++ b/postman/collection-dir/stripe/QuickStart/Payments - Create/request.json @@ -36,7 +36,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", diff --git a/postman/collection-dir/stripe/Refunds/Payments - Create/request.json b/postman/collection-dir/stripe/Refunds/Payments - Create/request.json index d3219a30cf8..657d42351cb 100644 --- a/postman/collection-dir/stripe/Refunds/Payments - Create/request.json +++ b/postman/collection-dir/stripe/Refunds/Payments - Create/request.json @@ -37,7 +37,7 @@ "payment_method_type": "credit", "payment_method_data": { "card": { - "card_number": "4242424242424242", + "card_number": "378282246310005", "card_exp_month": "10", "card_exp_year": "25", "card_holder_name": "joseph Doe", From 3af63a7c92033cd1aeedf90e31209de088e5b78d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 00:26:14 +0000 Subject: [PATCH 014/133] chore(postman): update Postman collection files --- .../stripe.postman_collection.json | 90 +++++++++---------- 1 file changed, 45 insertions(+), 45 deletions(-) diff --git a/postman/collection-json/stripe.postman_collection.json b/postman/collection-json/stripe.postman_collection.json index 329b444a41f..1f310172dc3 100644 --- a/postman/collection-json/stripe.postman_collection.json +++ b/postman/collection-json/stripe.postman_collection.json @@ -2505,7 +2505,7 @@ "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\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"},\"email\":\"example@example.com\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":{\"connector\":\"stripe\",\"merchant_connector_id\":\"{{merchant_connector_id}}\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"},\"email\":\"example@example.com\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":{\"connector\":\"stripe\",\"merchant_connector_id\":\"{{merchant_connector_id}}\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -2773,7 +2773,7 @@ "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\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":{\"connector\":\"stripe\",\"merchant_connector_id\":\"{{merchant_connector_id}}\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"return_url\":\"https://duck.com\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"},\"routing\":{\"type\":\"single\",\"data\":{\"connector\":\"stripe\",\"merchant_connector_id\":\"{{merchant_connector_id}}\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3609,7 +3609,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -3814,7 +3814,7 @@ "language": "json" } }, - "raw": "{\"amount\":20000,\"currency\":\"SGD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"email\":\"joseph@example.com\",\"name\":\"joseph Doe\",\"phone\":\"9123456789\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"payment_method\":\"card\",\"return_url\":\"https://duck.com\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":20000,\"currency\":\"SGD\",\"confirm\":false,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"email\":\"joseph@example.com\",\"name\":\"joseph Doe\",\"phone\":\"9123456789\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"no_three_ds\",\"payment_method\":\"card\",\"return_url\":\"https://duck.com\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id", @@ -4311,7 +4311,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4558,7 +4558,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -4841,7 +4841,7 @@ "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\":\"StripeCustomer\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -5272,7 +5272,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_issuer\":\"Visa\",\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"John Doe\"},\"customer_id\":\"{{customer_id}}\",\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_issuer\":\"Visa\",\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"John Doe\"},\"customer_id\":\"{{customer_id}}\",\"metadata\":{\"city\":\"NY\",\"unit\":\"245\"}}" }, "url": { "raw": "{{baseUrl}}/payment_methods", @@ -5470,7 +5470,7 @@ "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\":\"StripeCustomer\",\"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\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -6056,7 +6056,7 @@ "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\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"email\":\"guest@example.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+1\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -6593,7 +6593,7 @@ "language": "json" } }, - "raw": "{\"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\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"Joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -6862,7 +6862,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -7272,7 +7272,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":null,\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":null,\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -7681,7 +7681,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -8080,7 +8080,7 @@ "language": "json" } }, - "raw": "{\"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\"}},\"client_secret\":\"{{client_secret}}\"}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\"}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -8331,7 +8331,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -8723,7 +8723,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -9115,7 +9115,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -10435,7 +10435,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -10866,7 +10866,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -11636,7 +11636,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"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\"}}},\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"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\"}}},\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -12197,7 +12197,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"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\"}}},\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"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\"}}},\"customer_acceptance\":{\"acceptance_type\":\"online\",\"accepted_at\":\"2022-09-10T10:11:12Z\",\"online\":{\"ip_address\":\"123.32.25.123\",\"user_agent\":\"Mozilla/5.0 (Linux; Android 12; SM-S906N Build/QP1A.190711.020; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/80.0.3987.119 Mobile Safari/537.36\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -17419,7 +17419,7 @@ "language": "json" } }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"setup_future_usage\":\"on_session\",\"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\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" + "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"setup_future_usage\":\"on_session\",\"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\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"128.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -17643,7 +17643,7 @@ "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\":\"stripesavecard_1234\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4111111111111111\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"stripesavecard_1234\",\"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://google.com\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"credit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"03\",\"card_exp_year\":\"2030\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"737\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"joseph\",\"last_name\":\"Doe\"},\"phone\":{\"number\":\"9123456789\",\"country_code\":\"+91\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -19983,7 +19983,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"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\"}},\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -20343,7 +20343,7 @@ "language": "json" } }, - "raw": "{\"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\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"Joseph Doe\",\"card_cvc\":\"123\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -20501,7 +20501,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"business_country\":\"US\",\"business_label\":\"default\",\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"bernard123\",\"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\",\"setup_future_usage\":\"on_session\",\"payment_method\":\"card\",\"payment_method_type\":\"debit\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\",\"last_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -20836,7 +20836,7 @@ "language": "json" } }, - "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"01\",\"card_exp_year\":\"26\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}}" }, "url": { "raw": "{{baseUrl}}/payments/:id", @@ -21253,7 +21253,7 @@ "language": "json" } }, - "raw": "{\"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\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" + "raw": "{\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"Joseph Doe\",\"card_cvc\":\"123\"},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}},\"client_secret\":\"{{client_secret}}\",\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"125.0.0.1\"}}" }, "url": { "raw": "{{baseUrl}}/payments/:id/confirm", @@ -21468,7 +21468,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"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\":\"\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -21602,7 +21602,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"13\",\"card_exp_year\":\"69\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"13\",\"card_exp_year\":\"69\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -21679,7 +21679,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4242424242424242\",\"card_exp_month\":\"10\",\"card_exp_year\":\"22\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"22\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -21758,7 +21758,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"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\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -21913,7 +21913,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000000101\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"4000000000000101\",\"card_exp_month\":\"12\",\"card_exp_year\":\"34\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -21999,7 +21999,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\"}}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -22168,7 +22168,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"payment_method\":\"card\",\"payment_method_data\":{\"card\":{\"card_number\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrisoff Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"Narayan\",\"last_name\":\"Doe\"},\"email\":\"example@juspay.in\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -22295,7 +22295,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -22920,7 +22920,7 @@ "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\":\"StripeCustomer\",\"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\":\"01\",\"card_exp_year\":\"2023\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"01\",\"card_exp_year\":\"2023\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -23053,7 +23053,7 @@ "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\":\"StripeCustomer\",\"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\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"2022\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -23589,7 +23589,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"manual\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -23969,7 +23969,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -24230,7 +24230,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -24867,7 +24867,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -25204,7 +25204,7 @@ "language": "json" } }, - "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", @@ -25567,7 +25567,7 @@ "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\":\"StripeCustomer\",\"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\"}},\"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\"}}},\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" + "raw": "{\"amount\":6540,\"currency\":\"USD\",\"confirm\":true,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":6540,\"customer_id\":\"StripeCustomer\",\"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\":\"378282246310005\",\"card_exp_month\":\"10\",\"card_exp_year\":\"25\",\"card_holder_name\":\"joseph Doe\",\"card_cvc\":\"123\"}},\"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\"}}},\"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\"}},\"billing\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"sundari\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" }, "url": { "raw": "{{baseUrl}}/payments", From b5b50036088b72338156e29be23f77a62d673f30 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 31 Jan 2025 00:26:47 +0000 Subject: [PATCH 015/133] chore(version): 2025.01.31.0 --- CHANGELOG.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e571e3eac1d..8ed7f89e30e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,16 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.01.31.0 + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`3af63a7`](https://github.com/juspay/hyperswitch/commit/3af63a7c92033cd1aeedf90e31209de088e5b78d)) + +**Full Changelog:** [`2025.01.30.0...2025.01.31.0`](https://github.com/juspay/hyperswitch/compare/2025.01.30.0...2025.01.31.0) + +- - - + ## 2025.01.30.0 ### Features From 0e9966a54d87f55b0f5c54e4dccb80742674fe26 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Mon, 3 Feb 2025 19:22:14 +0530 Subject: [PATCH 016/133] chore: bump cypress to `v14.0.0` (#7102) --- cypress-tests/cypress.config.js | 45 +- .../cypress/e2e/configs/Payment/Nmi.js | 17 +- .../cypress/e2e/configs/Payment/Stripe.js | 12 +- .../cypress/e2e/configs/Payment/Utils.js | 34 +- cypress-tests/cypress/support/commands.js | 9 +- cypress-tests/cypress/support/e2e.js | 12 +- .../cypress/support/redirectionHandler.js | 882 ++++++++++-------- cypress-tests/cypress/utils/featureFlags.js | 46 +- cypress-tests/package-lock.json | 28 +- cypress-tests/package.json | 6 +- 10 files changed, 612 insertions(+), 479 deletions(-) diff --git a/cypress-tests/cypress.config.js b/cypress-tests/cypress.config.js index 913663c509e..dce9048339e 100644 --- a/cypress-tests/cypress.config.js +++ b/cypress-tests/cypress.config.js @@ -30,14 +30,27 @@ export default defineConfig({ }, }); on("after:spec", (spec, results) => { - if (results && results.video) { - // Do we have failures for any retry attempts? - const failures = results.tests.some((test) => + // Clean up resources after each spec + if ( + results && + results.video && + !results.tests.some((test) => test.attempts.some((attempt) => attempt.state === "failed") - ); - if (!failures) { - // delete the video if the spec passed and no tests retried - fs.unlinkSync(results.video); + ) + ) { + // Only try to delete if the video file exists + try { + if (fs.existsSync(results.video)) { + fs.unlinkSync(results.video); + } + } catch (error) { + // Log the error but don't fail the test + // eslint-disable-next-line no-console + console.warn( + `Warning: Could not delete video file: ${results.video}` + ); + // eslint-disable-next-line no-console + console.warn(error); } } }); @@ -45,6 +58,9 @@ export default defineConfig({ }, experimentalRunAllSpecs: true, + specPattern: "cypress/e2e/**/*.cy.{js,jsx,ts,tsx}", + supportFile: "cypress/support/e2e.js", + reporter: "cypress-mochawesome-reporter", reporterOptions: { reportDir: `cypress/reports/${connectorId}`, @@ -55,12 +71,13 @@ export default defineConfig({ inlineAssets: true, saveJson: true, }, + defaultCommandTimeout: 10000, + pageLoadTimeout: 20000, + responseTimeout: 30000, + screenshotsFolder: screenshotsFolderName, + video: true, + videoCompression: 32, + videosFolder: `cypress/videos/${connectorId}`, + chromeWebSecurity: false, }, - chromeWebSecurity: false, - defaultCommandTimeout: 10000, - pageLoadTimeout: 20000, - responseTimeout: 30000, - screenshotsFolder: screenshotsFolderName, - video: true, - videoCompression: 32, }); diff --git a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js index 8ad218953dd..e8cdd97e7a7 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js @@ -163,7 +163,9 @@ export const connectorDetails = { }, }, Void: { - Request: {}, + Request: { + cancellation_reason: "user_cancel", + }, Response: { status: 200, body: { @@ -172,16 +174,13 @@ export const connectorDetails = { }, }, VoidAfterConfirm: { - Request: {}, + Request: { + cancellation_reason: "user_cancel", + }, Response: { - status: 400, + status: 200, body: { - error: { - code: "IR_16", - message: - "You cannot cancel this payment because it has status processing", - type: "invalid_request", - }, + status: "processing", }, }, }, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js index d4d3c30fe27..b73bbf18dcc 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js @@ -23,7 +23,7 @@ const successfulThreeDSTestCardDetails = { const failedNo3DSCardDetails = { card_number: "4000000000000002", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "35", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -123,7 +123,7 @@ const requiredFields = { export const connectorDetails = { multi_credential_config: { - specName: ["connectorAgnostic"], + specName: ["connectorAgnosticNTID"], value: "connector_2", }, card_pm: { @@ -144,7 +144,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Configs: { CONNECTOR_CREDENTIAL: { - specName: ["connectorAgnostic"], + specName: ["connectorAgnosticNTID"], value: "connector_2", }, }, @@ -564,7 +564,7 @@ export const connectorDetails = { MITAutoCapture: getCustomExchange({ Configs: { CONNECTOR_CREDENTIAL: { - specName: ["connectorAgnostic"], + specName: ["connectorAgnosticNTID"], value: "connector_2", }, }, @@ -701,7 +701,7 @@ export const connectorDetails = { SaveCardUseNo3DSAutoCaptureOffSession: { Configs: { CONNECTOR_CREDENTIAL: { - specName: ["connectorAgnostic"], + specName: ["connectorAgnosticNTID"], value: "connector_2", }, }, @@ -754,7 +754,7 @@ export const connectorDetails = { SaveCardConfirmAutoCaptureOffSession: { Configs: { CONNECTOR_CREDENTIAL: { - specName: ["connectorAgnostic"], + specName: ["connectorAgnosticNTID"], value: "connector_2", }, }, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index c35e5950b73..5a9baa32d73 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -4,10 +4,8 @@ import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as bankOfAmericaConnectorDetails } from "./BankOfAmerica.js"; import { connectorDetails as bluesnapConnectorDetails } from "./Bluesnap.js"; import { connectorDetails as checkoutConnectorDetails } from "./Checkout.js"; -import { - connectorDetails as CommonConnectorDetails, - updateDefaultStatusCode, -} from "./Commons.js"; +import { connectorDetails as CommonConnectorDetails } from "./Commons.js"; +import { updateDefaultStatusCode } from "./Modifiers.js"; import { connectorDetails as cybersourceConnectorDetails } from "./Cybersource.js"; import { connectorDetails as datatransConnectorDetails } from "./Datatrans.js"; import { connectorDetails as elavonConnectorDetails } from "./Elavon.js"; @@ -227,12 +225,10 @@ function getConnectorConfig( const mcaConfig = getConnectorDetails(globalState.get("connectorId")); return { - config: { - CONNECTOR_CREDENTIAL: - multipleConnector?.nextConnector && multipleConnectors?.status - ? multipleConnector - : mcaConfig?.multi_credential_config || multipleConnector, - }, + CONNECTOR_CREDENTIAL: + multipleConnector?.nextConnector && multipleConnectors?.status + ? multipleConnector + : mcaConfig?.multi_credential_config || multipleConnector, multipleConnectors, }; } @@ -243,13 +239,12 @@ export function createBusinessProfile( globalState, multipleConnector = { nextConnector: false } ) { - const { config, multipleConnectors } = getConnectorConfig( - globalState, - multipleConnector - ); + const config = getConnectorConfig(globalState, multipleConnector); const { profilePrefix } = execConfig(config); - if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + if ( + shouldProceedWithOperation(multipleConnector, config.multipleConnectors) + ) { cy.createBusinessProfileTest( createBusinessProfileBody, globalState, @@ -266,13 +261,12 @@ export function createMerchantConnectorAccount( paymentMethodsEnabled, multipleConnector = { nextConnector: false } ) { - const { config, multipleConnectors } = getConnectorConfig( - globalState, - multipleConnector - ); + const config = getConnectorConfig(globalState, multipleConnector); const { profilePrefix, merchantConnectorPrefix } = execConfig(config); - if (shouldProceedWithOperation(multipleConnector, multipleConnectors)) { + if ( + shouldProceedWithOperation(multipleConnector, config.multipleConnectors) + ) { cy.createConnectorCallTest( paymentType, createMerchantConnectorAccountBody, diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 60691899f7c..45be7043116 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -620,7 +620,6 @@ Cypress.Commands.add( ); if (stateUpdate) { - // cy.task("setGlobalState", stateUpdate); globalState.set( "MULTIPLE_CONNECTORS", stateUpdate.MULTIPLE_CONNECTORS @@ -2202,13 +2201,19 @@ Cypress.Commands.add( ); Cypress.Commands.add("voidCallTest", (requestBody, data, globalState) => { - const { Configs: configs = {}, Response: resData } = data || {}; + const { + Configs: configs = {}, + Response: resData, + Request: reqData, + } = data || {}; const configInfo = execConfig(validateConfig(configs)); const payment_id = globalState.get("paymentID"); const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); requestBody.profile_id = profile_id; + requestBody.cancellation_reason = + reqData?.cancellation_reason ?? requestBody.cancellation_reason; cy.request({ method: "POST", diff --git a/cypress-tests/cypress/support/e2e.js b/cypress-tests/cypress/support/e2e.js index eab7b99b629..1ec43cd03b6 100644 --- a/cypress-tests/cypress/support/e2e.js +++ b/cypress-tests/cypress/support/e2e.js @@ -16,12 +16,22 @@ // Import commands.js using ES2015 syntax: import "cypress-mochawesome-reporter/register"; import "./commands"; +import "./redirectionHandler"; + +Cypress.on("window:before:load", (win) => { + // Add security headers + win.headers = { + "Content-Security-Policy": "default-src 'self'", + "X-Content-Type-Options": "nosniff", + "X-Frame-Options": "DENY", + }; +}); // Add error handling for dynamic imports Cypress.on("uncaught:exception", (err, runnable) => { // Log the error details // eslint-disable-next-line no-console - console.log( + console.error( `Error: ${err.message}\nError occurred in: ${runnable.title}\nStack trace: ${err.stack}` ); diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index cc9c180b35e..d840aab9e48 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -3,23 +3,38 @@ import jsQR from "jsqr"; // Define constants for wait times -const TIMEOUT = 20000; // 20 seconds -const WAIT_TIME = 10000; // 10 seconds +const CONSTANTS = { + TIMEOUT: 20000, // 20 seconds + WAIT_TIME: 10000, // 10 seconds + ERROR_PATTERNS: [ + /4\d{2}/, + /5\d{2}/, + /error/i, + /invalid request/i, + /server error/i, + ], + VALID_TERMINAL_STATUSES: [ + "failed", + "processing", + "requires_capture", + "succeeded", + ], +}; export function handleRedirection( - redirection_type, + redirectionType, urls, connectorId, - payment_method_type, - handler_metadata + paymentMethodType, + handlerMetadata ) { - switch (redirection_type) { + switch (redirectionType) { case "bank_redirect": bankRedirectRedirection( urls.redirection_url, urls.expected_url, connectorId, - payment_method_type + paymentMethodType ); break; case "bank_transfer": @@ -27,8 +42,8 @@ export function handleRedirection( urls.redirection_url, urls.expected_url, connectorId, - payment_method_type, - handler_metadata.next_action_type + paymentMethodType, + handlerMetadata.next_action_type ); break; case "three_ds": @@ -39,309 +54,358 @@ export function handleRedirection( urls.redirection_url, urls.expected_url, connectorId, - payment_method_type + paymentMethodType ); break; default: - throw new Error(`Redirection known: ${redirection_type}`); + throw new Error(`Unknown redirection type: ${redirectionType}`); } } function bankTransferRedirection( - redirection_url, - expected_url, + redirectionUrl, + expectedUrl, connectorId, - payment_method_type, - next_action_type + paymentMethodType, + nextActionType ) { - switch (next_action_type) { + switch (nextActionType) { case "qr_code_url": - cy.request(redirection_url.href).then((response) => { + cy.request(redirectionUrl.href).then((response) => { switch (connectorId) { case "adyen": - switch (payment_method_type) { + switch (paymentMethodType) { case "pix": expect(response.status).to.eq(200); - fetchAndParseQRCode(redirection_url.href).then((qrCodeData) => { + fetchAndParseQRCode(redirectionUrl.href).then((qrCodeData) => { expect(qrCodeData).to.eq("TestQRCodeEMVToken"); }); break; default: - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); // expected_redirection can be used here to handle other payment methods } break; default: - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); } }); break; case "image_data_url": switch (connectorId) { case "itaubank": - switch (payment_method_type) { + switch (paymentMethodType) { case "pix": - fetchAndParseImageData(redirection_url).then((qrCodeData) => { + fetchAndParseImageData(redirectionUrl).then((qrCodeData) => { expect(qrCodeData).to.contains("itau.com.br/pix/qr/v2"); // image data contains the following value }); break; default: - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); } break; default: - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); } break; default: - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); } } function bankRedirectRedirection( - redirection_url, - expected_url, + redirectionUrl, + expectedUrl, connectorId, - payment_method_type + paymentMethodType ) { let verifyUrl = false; - cy.visit(redirection_url.href); - - switch (connectorId) { - case "adyen": - switch (payment_method_type) { - case "eps": - cy.get("h1").should("contain.text", "Acquirer Simulator"); - cy.get('[value="authorised"]').click(); - cy.url().should("include", "status=succeeded"); - cy.wait(5000); - break; - case "ideal": - cy.get(":nth-child(4) > td > p").should( - "contain.text", - "Your Payment was Authorised/Refused/Cancelled (It may take up to five minutes to show on the Payment List)" - ); - cy.get(".btnLink").click(); - cy.url().should("include", "status=succeeded"); - cy.wait(5000); - break; - case "giropay": - cy.get( - ".rds-cookies-overlay__allow-all-cookies-btn > .rds-button" - ).click(); - cy.wait(5000); - cy.get(".normal-3").should( - "contain.text", - "Bank suchen ‑ mit giropay zahlen." - ); - cy.get("#bankSearch").type("giropay TestBank{enter}"); - cy.get(".normal-2 > div").click(); - cy.get('[data-testid="customerIban"]').type("DE48499999601234567890"); - cy.get('[data-testid="customerIdentification"]').type("9123456789"); - cy.get(":nth-child(3) > .rds-button").click(); - cy.get('[data-testid="onlineBankingPin"]').type("1234"); - cy.get(".rds-button--primary").click(); - cy.get(":nth-child(5) > .rds-radio-input-group__label").click(); - cy.get(".rds-button--primary").click(); - cy.get('[data-testid="photoTan"]').type("123456"); - cy.get(".rds-button--primary").click(); - cy.wait(5000); - cy.url().should("include", "status=succeeded"); - cy.wait(5000); - break; - case "sofort": - cy.get(".modal-overlay.modal-shown.in", { timeout: TIMEOUT }).then( - ($modal) => { - // If modal is found, handle it - if ($modal.length > 0) { - cy.get("button.cookie-modal-deny-all.button-tertiary") - .should("be.visible") - .should("contain", "Reject All") - .click({ multiple: true }); - cy.get("div#TopBanks.top-banks-multistep") - .should("contain", "Demo Bank") - .as("btn") - .click(); - cy.get("@btn").click(); - } else { - cy.get("input.phone").type("9123456789"); - cy.get("#button.onContinue") - .should("contain", "Continue") - .click(); - } - } - ); - break; - case "trustly": - break; - default: - throw new Error( - `Unsupported payment method type: ${payment_method_type}` - ); - } - verifyUrl = true; - break; - case "paypal": - switch (payment_method_type) { - case "eps": - cy.get('button[name="Successful"][value="SUCCEEDED"]').click(); - break; - case "ideal": - cy.get('button[name="Successful"][value="SUCCEEDED"]').click(); - break; - case "giropay": - cy.get('button[name="Successful"][value="SUCCEEDED"]').click(); - break; - default: - throw new Error( - `Unsupported payment method type: ${payment_method_type}` - ); - } - verifyUrl = true; - break; - case "stripe": - switch (payment_method_type) { - case "eps": - cy.get('a[name="success"]').click(); - break; - case "ideal": - cy.get('a[name="success"]').click(); - break; - case "giropay": - cy.get('a[name="success"]').click(); - break; - case "sofort": - cy.get('a[name="success"]').click(); - break; - case "przelewy24": - cy.get('a[name="success"]').click(); - break; - default: - throw new Error( - `Unsupported payment method type: ${payment_method_type}` - ); - } - verifyUrl = true; - break; - case "trustpay": - switch (payment_method_type) { - case "eps": - cy.get("#bankname").type( - "Allgemeine Sparkasse Oberösterreich Bank AG (ASPKAT2LXXX / 20320)" - ); - cy.get("#selectionSubmit").click(); - cy.get("#user") - .should("be.visible") - .should("be.enabled") - .focus() - .type("Verfügernummer"); - cy.get("input#submitButton.btn.btn-primary").click(); - break; - case "ideal": - cy.contains("button", "Select your bank").click(); - cy.get( - 'button[data-testid="bank-item"][id="bank-item-INGBNL2A"]' - ).click(); + + cy.visit(redirectionUrl.href); + waitForRedirect(redirectionUrl.href); + + handleFlow( + redirectionUrl, + expectedUrl, + connectorId, + ({ connectorId, paymentMethodType, constants }) => { + switch (connectorId) { + case "adyen": + switch (paymentMethodType) { + case "eps": + cy.get("h1").should("contain.text", "Acquirer Simulator"); + cy.get('[value="authorised"]').click(); + break; + case "ideal": + cy.get(":nth-child(4) > td > p").should( + "contain.text", + "Your Payment was Authorised/Refused/Cancelled (It may take up to five minutes to show on the Payment List)" + ); + cy.get(".btnLink").click(); + cy.url().should("include", "status=succeeded"); + cy.wait(5000); + break; + case "giropay": + cy.get( + ".rds-cookies-overlay__allow-all-cookies-btn > .rds-button" + ).click(); + cy.wait(5000); + cy.get(".normal-3").should( + "contain.text", + "Bank suchen ‑ mit giropay zahlen." + ); + cy.get("#bankSearch").type("giropay TestBank{enter}"); + cy.get(".normal-2 > div").click(); + cy.get('[data-testid="customerIban"]').type( + "DE48499999601234567890" + ); + cy.get('[data-testid="customerIdentification"]').type( + "9123456789" + ); + cy.get(":nth-child(3) > .rds-button").click(); + cy.get('[data-testid="onlineBankingPin"]').type("1234"); + cy.get(".rds-button--primary").click(); + cy.get(":nth-child(5) > .rds-radio-input-group__label").click(); + cy.get(".rds-button--primary").click(); + cy.get('[data-testid="photoTan"]').type("123456"); + cy.get(".rds-button--primary").click(); + cy.wait(5000); + cy.url().should("include", "status=succeeded"); + cy.wait(5000); + break; + case "sofort": + cy.get(".modal-overlay.modal-shown.in", { + timeout: constants.TIMEOUT, + }).then(($modal) => { + // If modal is found, handle it + if ($modal.length > 0) { + cy.get("button.cookie-modal-deny-all.button-tertiary") + .should("be.visible") + .should("contain", "Reject All") + .click({ multiple: true }); + cy.get("div#TopBanks.top-banks-multistep") + .should("contain", "Demo Bank") + .as("btn") + .click(); + cy.get("@btn").click(); + } else { + cy.get("input.phone").type("9123456789"); + cy.get("#button.onContinue") + .should("contain", "Continue") + .click(); + } + }); + break; + default: + throw new Error( + `Unsupported payment method type: ${paymentMethodType}` + ); + } + verifyUrl = true; break; - case "giropay": - cy.get("._transactionId__header__iXVd_").should( - "contain.text", - "Bank suchen ‑ mit giropay zahlen." - ); - cy.get(".BankSearch_searchInput__uX_9l").type( - "Volksbank Hildesheim{enter}" - ); - cy.get(".BankSearch_searchIcon__EcVO7").click(); - cy.get(".BankSearch_bankWrapper__R5fUK").click(); - cy.get("._transactionId__primaryButton__nCa0r").click(); - cy.get(".normal-3").should("contain.text", "Kontoauswahl"); + case "paypal": + if (["eps", "ideal", "giropay"].includes(paymentMethodType)) { + cy.get('button[name="Successful"][value="SUCCEEDED"]').click(); + verifyUrl = true; + } else { + throw new Error( + `Unsupported payment method type: ${paymentMethodType}` + ); + } + verifyUrl = true; break; - case "sofort": + case "stripe": + if ( + ["eps", "ideal", "giropay", "sofort", "przelewy24"].includes( + paymentMethodType + ) + ) { + cy.get('a[name="success"]').click(); + verifyUrl = true; + } else { + throw new Error( + `Unsupported payment method type: ${paymentMethodType}` + ); + } + verifyUrl = true; break; - case "trustly": + case "trustpay": + switch (paymentMethodType) { + case "eps": + cy.get("#bankname").type( + "Allgemeine Sparkasse Oberösterreich Bank AG (ASPKAT2LXXX / 20320)" + ); + cy.get("#selectionSubmit").click(); + cy.get("#user") + .should("be.visible") + .should("be.enabled") + .focus() + .type("Verfügernummer"); + cy.get("input#submitButton.btn.btn-primary").click(); + break; + case "ideal": + cy.contains("button", "Select your bank").click(); + cy.get( + 'button[data-testid="bank-item"][id="bank-item-INGBNL2A"]' + ).click(); + break; + case "giropay": + cy.get("._transactionId__header__iXVd_").should( + "contain.text", + "Bank suchen ‑ mit giropay zahlen." + ); + cy.get(".BankSearch_searchInput__uX_9l").type( + "Volksbank Hildesheim{enter}" + ); + cy.get(".BankSearch_searchIcon__EcVO7").click(); + cy.get(".BankSearch_bankWrapper__R5fUK").click(); + cy.get("._transactionId__primaryButton__nCa0r").click(); + cy.get(".normal-3").should("contain.text", "Kontoauswahl"); + break; + default: + throw new Error( + `Unsupported payment method type: ${paymentMethodType}` + ); + } + verifyUrl = false; break; default: - throw new Error( - `Unsupported payment method type: ${payment_method_type}` - ); + throw new Error(`Unsupported connector: ${connectorId}`); } - verifyUrl = false; - break; - default: - throw new Error(`Unsupported connector: ${connectorId}`); - } + }, + { paymentMethodType } + ); cy.then(() => { - verifyReturnUrl(redirection_url, expected_url, verifyUrl); + verifyReturnUrl(redirectionUrl, expectedUrl, verifyUrl); }); } -function threeDsRedirection(redirection_url, expected_url, connectorId) { - cy.visit(redirection_url.href); - - switch (connectorId) { - case "adyen": - cy.get("iframe") - .its("0.contentDocument.body") - .within(() => { - cy.get('input[type="password"]').click(); - cy.get('input[type="password"]').type("password"); - cy.get("#buttonSubmit").click(); - }); - break; +function threeDsRedirection(redirectionUrl, expectedUrl, connectorId) { + cy.visit(redirectionUrl.href); + waitForRedirect(redirectionUrl.href); - case "bankofamerica": - case "wellsfargo": - cy.get("iframe", { timeout: TIMEOUT }) - .should("be.visible") - .its("0.contentDocument.body") - .should("not.be.empty") - .within(() => { - cy.get( - 'input[type="text"], input[type="password"], input[name="challengeDataEntry"]', - { timeout: TIMEOUT } - ) - .should("be.visible") - .should("be.enabled") - .click() - .type("1234"); + handleFlow( + redirectionUrl, + expectedUrl, + connectorId, + ({ connectorId, constants, expectedUrl }) => { + switch (connectorId) { + case "adyen": + cy.get("iframe") + .its("0.contentDocument.body") + .within(() => { + cy.get('input[type="password"]').click(); + cy.get('input[type="password"]').type("password"); + cy.get("#buttonSubmit").click(); + }); + break; - cy.get('input[value="SUBMIT"], button[type="submit"]', { - timeout: TIMEOUT, - }) + case "bankofamerica": + case "wellsfargo": + cy.get("iframe", { timeout: constants.TIMEOUT }) .should("be.visible") - .click(); - }); - break; + .its("0.contentDocument.body") + .should("not.be.empty") + .within(() => { + cy.get( + 'input[type="text"], input[type="password"], input[name="challengeDataEntry"]', + { timeout: constants.TIMEOUT } + ) + .should("be.visible") + .should("be.enabled") + .click() + .type("1234"); + + cy.get('input[value="SUBMIT"], button[type="submit"]', { + timeout: constants.TIMEOUT, + }) + .should("be.visible") + .click(); + }); + break; - case "cybersource": - cy.url({ timeout: TIMEOUT }).should("include", expected_url.origin); - break; + case "cybersource": + cy.url({ timeout: constants.TIMEOUT }).should("include", expectedUrl); + break; + + case "checkout": + cy.get("iframe", { timeout: constants.TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get('form[id="form"]', { timeout: constants.WAIT_TIME }) + .should("exist") + .then(() => { + cy.get('input[id="password"]').click(); + cy.get('input[id="password"]').type("Checkout1!"); + cy.get("#txtButton").click(); + }); + }); + break; - case "checkout": - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.body") - .within(() => { - cy.get('form[id="form"]', { timeout: WAIT_TIME }) + case "nmi": + case "noon": + case "xendit": + cy.get("iframe", { timeout: constants.TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get("iframe", { timeout: constants.TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get('form[name="cardholderInput"]', { + timeout: constants.TIMEOUT, + }) + .should("exist") + .then(() => { + cy.get('input[name="challengeDataEntry"]') + .click() + .type("1234"); + cy.get('input[value="SUBMIT"]').click(); + }); + }); + }); + break; + + case "novalnet": + cy.get("form", { timeout: constants.WAIT_TIME }) .should("exist") .then(() => { - cy.get('input[id="password"]').click(); - cy.get('input[id="password"]').type("Checkout1!"); - cy.get("#txtButton").click(); + cy.get('input[id="submit"]').click(); }); - }); - break; + break; + + case "stripe": + cy.get("iframe", { timeout: constants.TIMEOUT }) + .its("0.contentDocument.body") + .within(() => { + cy.get("iframe") + .its("0.contentDocument.body") + .within(() => { + cy.get("#test-source-authorize-3ds").click(); + }); + }); + break; + + case "trustpay": + cy.get('form[name="challengeForm"]', { + timeout: constants.WAIT_TIME, + }) + .should("exist") + .then(() => { + cy.get("#outcomeSelect") + .select("Approve") + .should("have.value", "Y"); + cy.get('button[type="submit"]').click(); + }); + break; - case "nmi": - case "noon": - case "xendit": - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.body") - .within(() => { - cy.get("iframe", { timeout: TIMEOUT }) + case "worldpay": + cy.get("iframe", { timeout: constants.WAIT_TIME }) .its("0.contentDocument.body") .within(() => { - cy.get('form[name="cardholderInput"]', { timeout: TIMEOUT }) + cy.get('form[name="cardholderInput"]', { + timeout: constants.WAIT_TIME, + }) .should("exist") .then(() => { cy.get('input[name="challengeDataEntry"]') @@ -350,91 +414,50 @@ function threeDsRedirection(redirection_url, expected_url, connectorId) { cy.get('input[value="SUBMIT"]').click(); }); }); - }); - break; - - case "novalnet": - cy.get("form", { timeout: WAIT_TIME }) - .should("exist") - .then(() => { - cy.get('input[id="submit"]').click(); - }); - break; - - case "stripe": - cy.get("iframe", { timeout: TIMEOUT }) - .its("0.contentDocument.body") - .within(() => { - cy.get("iframe") - .its("0.contentDocument.body") - .within(() => { - cy.get("#test-source-authorize-3ds").click(); - }); - }); - break; - - case "trustpay": - cy.get('form[name="challengeForm"]', { timeout: WAIT_TIME }) - .should("exist") - .then(() => { - cy.get("#outcomeSelect").select("Approve").should("have.value", "Y"); - cy.get('button[type="submit"]').click(); - }); - break; + break; - case "worldpay": - cy.get("iframe", { timeout: WAIT_TIME }) - .its("0.contentDocument.body") - .within(() => { - cy.get('form[name="cardholderInput"]', { timeout: WAIT_TIME }) + case "fiuu": + cy.get('form[id="cc_form"]', { timeout: constants.TIMEOUT }) .should("exist") .then(() => { - cy.get('input[name="challengeDataEntry"]').click().type("1234"); - cy.get('input[value="SUBMIT"]').click(); - }); - }); - break; - - case "fiuu": - cy.get('form[id="cc_form"]', { timeout: TIMEOUT }) - .should("exist") - .then(() => { - cy.get('button.pay-btn[name="pay"]').click(); - cy.get("div.otp") - .invoke("text") - .then((otpText) => { - const otp = otpText.match(/\d+/)[0]; - cy.get("input#otp-input").should("not.be.disabled").type(otp); - cy.get("button.pay-btn").click(); + cy.get('button.pay-btn[name="pay"]').click(); + cy.get("div.otp") + .invoke("text") + .then((otpText) => { + const otp = otpText.match(/\d+/)[0]; + cy.get("input#otp-input").should("not.be.disabled").type(otp); + cy.get("button.pay-btn").click(); + }); }); - }); - break; + break; - default: - cy.wait(WAIT_TIME); - } + default: + cy.wait(constants.WAIT_TIME); + } + } + ); // Verify return URL after handling the specific connector - verifyReturnUrl(redirection_url, expected_url, true); + verifyReturnUrl(redirectionUrl, expectedUrl, true); } function upiRedirection( - redirection_url, - expected_url, + redirectionUrl, + expectedUrl, connectorId, - payment_method_type + paymentMethodType ) { let verifyUrl = false; if (connectorId === "iatapay") { - switch (payment_method_type) { + switch (paymentMethodType) { case "upi_collect": - cy.visit(redirection_url.href); - cy.wait(TIMEOUT).then(() => { + cy.visit(redirectionUrl.href); + cy.wait(CONSTANTS.TIMEOUT).then(() => { verifyUrl = true; }); break; case "upi_intent": - cy.request(redirection_url.href).then((response) => { + cy.request(redirectionUrl.href).then((response) => { expect(response.status).to.eq(200); expect(response.body).to.have.property("iataPaymentId"); expect(response.body).to.have.property("status", "INITIATED"); @@ -446,99 +469,95 @@ function upiRedirection( break; default: throw new Error( - `Unsupported payment method type: ${payment_method_type}` + `Unsupported payment method type: ${paymentMethodType}` ); } } else { - // If connectorId is not iatapay, wait for 10 seconds - cy.wait(WAIT_TIME); + // For other connectors, nothing to do + return; } cy.then(() => { - verifyReturnUrl(redirection_url, expected_url, verifyUrl); + verifyReturnUrl(redirectionUrl, expectedUrl, verifyUrl); }); } -function verifyReturnUrl(redirection_url, expected_url, forward_flow) { - if (!forward_flow) return; - - try { - if (redirection_url.host.endsWith(expected_url.host)) { - cy.wait(WAIT_TIME / 2); - - cy.window() - .its("location") - .then((location) => { - // Check page state before taking screenshots - cy.document().then((doc) => { - // For blank page - cy.wrap(doc.body.innerText.trim()).then((text) => { - if (text === "") { - cy.wrap(text).should("eq", ""); - cy.screenshot("blank-page-error"); - } - }); +function verifyReturnUrl(redirectionUrl, expectedUrl, forwardFlow) { + if (!forwardFlow) return; - // For error pages - const errorPatterns = [ - /4\d{2}/, - /5\d{2}/, - /error/i, - /invalid request/i, - /server error/i, - ]; - - const pageText = doc.body.innerText.toLowerCase(); - cy.wrap(pageText).then((text) => { - if (errorPatterns.some((pattern) => pattern.test(text))) { - cy.wrap(text).should((content) => { - expect(errorPatterns.some((pattern) => pattern.test(content))) - .to.be.true; - }); - cy.screenshot(`error-page-${Date.now()}`); - } - }); - }); + cy.location("host", { timeout: CONSTANTS.TIMEOUT }).should((currentHost) => { + expect(currentHost).to.equal(expectedUrl.host); + }); - const url_params = new URLSearchParams(location.search); - const payment_status = url_params.get("status"); + cy.url().then((url) => { + cy.origin( + new URL(url).origin, + { + args: { + redirectionUrl: redirectionUrl.origin, + expectedUrl: expectedUrl.origin, + constants: CONSTANTS, + }, + }, + ({ redirectionUrl, expectedUrl, constants }) => { + try { + const redirectionHost = new URL(redirectionUrl).host; + const expectedHost = new URL(expectedUrl).host; + if (redirectionHost.endsWith(expectedHost)) { + cy.wait(constants.WAIT_TIME / 2); + + cy.window() + .its("location") + .then((location) => { + // Check page state before taking screenshots + cy.document().then((doc) => { + const pageText = doc.body.innerText.toLowerCase(); + if (!pageText) { + // eslint-disable-next-line cypress/assertion-before-screenshot + cy.screenshot("blank-page-error"); + } else if ( + constants.ERROR_PATTERNS.some((pattern) => + pattern.test(pageText) + ) + ) { + // eslint-disable-next-line cypress/assertion-before-screenshot + cy.screenshot(`error-page-${Date.now()}`); + } + }); - if ( - payment_status !== "failed" && - payment_status !== "processing" && - payment_status !== "requires_capture" && - payment_status !== "succeeded" - ) { - cy.wrap(payment_status).should("exist"); - cy.screenshot(`failed-payment-${payment_status}`); - throw new Error( - `Redirection failed with payment status: ${payment_status}` - ); + const urlParams = new URLSearchParams(location.search); + const paymentStatus = urlParams.get("status"); + + if ( + !constants.VALID_TERMINAL_STATUSES.includes(paymentStatus) + ) { + // eslint-disable-next-line cypress/assertion-before-screenshot + cy.screenshot(`failed-payment-${paymentStatus}`); + throw new Error( + `Redirection failed with payment status: ${paymentStatus}` + ); + } + }); + } else { + cy.window().its("location.origin").should("eq", expectedUrl); + + Cypress.on("uncaught:exception", (err, runnable) => { + // Log the error details + // eslint-disable-next-line no-console + console.error( + `Error: ${err.message}\nOccurred in: ${runnable.title}\nStack: ${err.stack}` + ); + + // Return false to prevent the error from failing the test + return false; + }); } - }); - } else { - cy.origin( - expected_url.origin, - { args: { expected_url: expected_url.origin } }, - ({ expected_url }) => { - cy.window().its("location.origin").should("eq", expected_url); - - Cypress.on("uncaught:exception", (err, runnable) => { - // Log the error details - // eslint-disable-next-line no-console - console.log( - `Error: ${err.message}\nError occurred in: ${runnable.title}\nStack trace: ${err.stack}` - ); - // Return false to prevent the error from failing the test - return false; - }); + } catch (error) { + throw new Error(`Redirection verification failed: ${error}`); } - ); - } - } catch (error) { - cy.error("Redirection verification failed:", error); - throw error; - } + } + ); + }); } async function fetchAndParseQRCode(url) { @@ -548,12 +567,17 @@ async function fetchAndParseQRCode(url) { } const blob = await response.blob(); const reader = new FileReader(); - return await new Promise((resolve, reject) => { + + return new Promise((resolve, reject) => { reader.onload = () => { - const base64Image = reader.result.split(",")[1]; // Remove data URI prefix + // Use the entire data URI from reader.result + const dataUrl = reader.result; + + // Create a new Image, assigning its src to the full data URI const image = new Image(); - image.src = base64Image; + image.src = dataUrl; + // Once the image loads, draw it to a canvas and let jsQR decode it image.onload = () => { const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); @@ -574,8 +598,14 @@ async function fetchAndParseQRCode(url) { reject(new Error("Failed to decode QR code")); } }; - image.onerror = reject; // Handle image loading errors + + // If the image fails to load at all, reject the promise + image.onerror = (err) => { + reject(new Error("Image failed to load: " + err?.message || err)); + }; }; + + // Read the blob as a data URL (this includes the data:image/png;base64 prefix) reader.readAsDataURL(blob); }); } @@ -608,3 +638,69 @@ async function fetchAndParseImageData(url) { image.onerror = reject; // Handle image loading errors }); } + +function waitForRedirect(redirectionUrl) { + const originalHost = new URL(redirectionUrl).host; + + cy.location("host", { timeout: CONSTANTS.TIMEOUT }).should((currentHost) => { + const hostChanged = currentHost !== originalHost; + const iframeExists = Cypress.$("iframe") + .toArray() + .some((iframeEl) => { + try { + const iframeHost = new URL(iframeEl.src).host; + return iframeHost && iframeHost !== originalHost; + } catch { + return false; + } + }); + + // The assertion will pass if either the host changed or an iframe with a foreign host exists. + expect( + hostChanged || iframeExists, + "Host changed or an iframe with foreign host exist" + ).to.be.true; + }); +} + +function handleFlow( + redirectionUrl, + expectedUrl, + connectorId, + callback, + options = {} +) { + const originalHost = new URL(redirectionUrl.href).host; + cy.location("host", { timeout: CONSTANTS.TIMEOUT }).then((currentHost) => { + if (currentHost !== originalHost) { + // Regular redirection flow - host changed, use cy.origin() + cy.url().then((currentUrl) => { + cy.origin( + new URL(currentUrl).origin, + { + args: { + connectorId, + constants: CONSTANTS, + expectedUrl: expectedUrl.origin, + ...options, // optional params like paymentMethodType + }, + }, + callback + ); + }); + } else { + // Embedded flow - host unchanged, use cy.get("iframe") + cy.get("iframe", { timeout: CONSTANTS.TIMEOUT }) + .should("be.visible") + .should("exist"); + + // Execute callback within the iframe context + callback({ + connectorId, + constants: CONSTANTS, + expectedUrl: expectedUrl.origin, + ...options, // optional params like paymentMethodType + }); + } + }); +} diff --git a/cypress-tests/cypress/utils/featureFlags.js b/cypress-tests/cypress/utils/featureFlags.js index 6d8599820f7..728016ebbb5 100644 --- a/cypress-tests/cypress/utils/featureFlags.js +++ b/cypress-tests/cypress/utils/featureFlags.js @@ -149,29 +149,41 @@ function matchesSpecName(specName) { ); } -export function determineConnectorConfig(connectorConfig) { - // Case 1: Multiple connectors configuration - if ( - connectorConfig?.nextConnector && - connectorConfig?.multipleConnectors?.status - ) { - return "connector_2"; - } +export function determineConnectorConfig(config) { + const connectorCredential = config?.CONNECTOR_CREDENTIAL; + const multipleConnectors = config?.multipleConnectors; - // Case 2: Invalid or null configuration - if (!connectorConfig || connectorConfig.value === "null") { + // If CONNECTOR_CREDENTIAL doesn't exist or value is null, return default + if (!connectorCredential || connectorCredential.value === null) { return DEFAULT_CONNECTOR; } - const { specName, value } = connectorConfig; + // Handle nextConnector cases + if ( + Object.prototype.hasOwnProperty.call(connectorCredential, "nextConnector") + ) { + if (connectorCredential.nextConnector === true) { + // Check multipleConnectors conditions if available + if ( + multipleConnectors?.status === true && + multipleConnectors?.count > 1 + ) { + return "connector_2"; + } + return DEFAULT_CONNECTOR; + } + return DEFAULT_CONNECTOR; + } - // Case 3: No spec name matching needed - if (!specName) { - return value; + // Handle specName cases + if (Object.prototype.hasOwnProperty.call(connectorCredential, "specName")) { + return matchesSpecName(connectorCredential.specName) + ? connectorCredential.value + : DEFAULT_CONNECTOR; } - // Case 4: Match spec name and return appropriate connector - return matchesSpecName(specName) ? value : DEFAULT_CONNECTOR; + // Return value if it's the only property + return connectorCredential.value; } export function execConfig(configs) { @@ -179,7 +191,7 @@ export function execConfig(configs) { cy.wait(configs.DELAY.TIMEOUT); } - const connectorType = determineConnectorConfig(configs?.CONNECTOR_CREDENTIAL); + const connectorType = determineConnectorConfig(configs); const { profileId, connectorId } = getProfileAndConnectorId(connectorType); return { diff --git a/cypress-tests/package-lock.json b/cypress-tests/package-lock.json index 57aef9f3b3e..abb5bd3cec5 100644 --- a/cypress-tests/package-lock.json +++ b/cypress-tests/package-lock.json @@ -9,10 +9,10 @@ "version": "1.0.0", "license": "ISC", "devDependencies": { - "@eslint/js": "^9.18.0", - "cypress": "^13.17.0", + "@eslint/js": "^9.19.0", + "cypress": "^14.0.0", "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.18.0", + "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-prettier": "^5.2.3", @@ -191,9 +191,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.18.0.tgz", - "integrity": "sha512-fK6L7rxcq6/z+AaQMtiFTkvbHkBLNlwyRxHpKawP0x3u9+NC6MQTnFW+AdpwC6gfHTW0051cokQgtTN2FqlxQA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.19.0.tgz", + "integrity": "sha512-rbq9/g38qjfqFLOVPvwjIvFFdNziEC5S65jmjPw5r6A//QH+W91akh9irMwjDN8zKUTak6W9EsAv4m/7Wnw0UQ==", "dev": true, "license": "MIT", "engines": { @@ -1023,9 +1023,9 @@ } }, "node_modules/cypress": { - "version": "13.17.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.17.0.tgz", - "integrity": "sha512-5xWkaPurwkIljojFidhw8lFScyxhtiFHl/i/3zov+1Z5CmY4t9tjIdvSXfu82Y3w7wt0uR9KkucbhkVvJZLQSA==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-14.0.0.tgz", + "integrity": "sha512-kEGqQr23so5IpKeg/dp6GVi7RlHx1NmW66o2a2Q4wk9gRaAblLZQSiZJuDI8UMC4LlG5OJ7Q6joAiqTrfRNbTw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -1078,7 +1078,7 @@ "cypress": "bin/cypress" }, "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" } }, "node_modules/cypress-mochawesome-reporter": { @@ -1335,9 +1335,9 @@ } }, "node_modules/eslint": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.18.0.tgz", - "integrity": "sha512-+waTfRWQlSbpt3KWE+CjrPPYnbq9kfZIYUqapc0uBXyjTp8aYXZDsUH16m39Ryq3NjAVP4tjuF7KaukeqoCoaA==", + "version": "9.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.19.0.tgz", + "integrity": "sha512-ug92j0LepKlbbEv6hD911THhoRHmbdXt2gX+VDABAW/Ir7D3nqKdv5Pf5vtlyY6HQMTEP2skXY43ueqTCWssEA==", "dev": true, "license": "MIT", "dependencies": { @@ -1346,7 +1346,7 @@ "@eslint/config-array": "^0.19.0", "@eslint/core": "^0.10.0", "@eslint/eslintrc": "^3.2.0", - "@eslint/js": "9.18.0", + "@eslint/js": "9.19.0", "@eslint/plugin-kit": "^0.2.5", "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", diff --git a/cypress-tests/package.json b/cypress-tests/package.json index a2e0a961e8f..b3701ce8fc7 100644 --- a/cypress-tests/package.json +++ b/cypress-tests/package.json @@ -20,10 +20,10 @@ "author": "Hyperswitch", "license": "ISC", "devDependencies": { - "@eslint/js": "^9.18.0", - "cypress": "^13.17.0", + "@eslint/js": "^9.19.0", + "cypress": "^14.0.0", "cypress-mochawesome-reporter": "^3.8.2", - "eslint": "^9.18.0", + "eslint": "^9.19.0", "eslint-config-prettier": "^10.0.1", "eslint-plugin-cypress": "^4.1.0", "eslint-plugin-prettier": "^5.2.3", From 64a7afa6d42270d96788119e666b97176cd753dd Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Mon, 3 Feb 2025 23:55:22 +0530 Subject: [PATCH 017/133] fix(connector): [NETCETERA] add `sdk-type` and `default-sdk-type` in netcetera authentication request (#7156) --- api-reference-v2/openapi_spec.json | 19 +++++++++++++ api-reference/openapi_spec.json | 19 +++++++++++++ crates/api_models/src/payments.rs | 17 ++++++++++++ crates/openapi/src/openapi.rs | 1 + crates/openapi/src/openapi_v2.rs | 1 + .../connector/netcetera/netcetera_types.rs | 27 ++++++++++++++++--- 6 files changed, 80 insertions(+), 4 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 13014a44e0e..236c0af3a38 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -19982,6 +19982,14 @@ "format": "int32", "description": "Indicates maximum amount of time in minutes", "minimum": 0 + }, + "sdk_type": { + "allOf": [ + { + "$ref": "#/components/schemas/SdkType" + } + ], + "nullable": true } } }, @@ -20011,6 +20019,17 @@ } } }, + "SdkType": { + "type": "string", + "description": "Enum representing the type of 3DS SDK.", + "enum": [ + "01", + "02", + "03", + "04", + "05" + ] + }, "SecretInfoToInitiateSdk": { "type": "object", "required": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d218e4d2074..9b840f53138 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -24523,6 +24523,14 @@ "format": "int32", "description": "Indicates maximum amount of time in minutes", "minimum": 0 + }, + "sdk_type": { + "allOf": [ + { + "$ref": "#/components/schemas/SdkType" + } + ], + "nullable": true } } }, @@ -24552,6 +24560,17 @@ } } }, + "SdkType": { + "type": "string", + "description": "Enum representing the type of 3DS SDK.", + "enum": [ + "01", + "02", + "03", + "04", + "05" + ] + }, "SecretInfoToInitiateSdk": { "type": "object", "required": [ diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b6152cc9787..2c0a09bf8f0 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6548,6 +6548,23 @@ pub struct SdkInformation { pub sdk_reference_number: String, /// Indicates maximum amount of time in minutes pub sdk_max_timeout: u8, + /// Indicates the type of 3DS SDK + pub sdk_type: Option, +} + +/// Enum representing the type of 3DS SDK. +#[derive(Serialize, Deserialize, Debug, Clone, ToSchema)] +pub enum SdkType { + #[serde(rename = "01")] + DefaultSdk, + #[serde(rename = "02")] + SplitSdk, + #[serde(rename = "03")] + LimitedSdk, + #[serde(rename = "04")] + BrowserSdk, + #[serde(rename = "05")] + ShellSdk, } #[cfg(feature = "v2")] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 3a634ae2a54..de358f79332 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -653,6 +653,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PaymentChargeType, api_models::enums::StripeChargeType, api_models::payments::CustomerDetailsResponse, + api_models::payments::SdkType, api_models::payments::OpenBankingData, api_models::payments::OpenBankingSessionToken, api_models::payments::BankDebitResponse, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 3b1fefefa15..701fb22d1ad 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -328,6 +328,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::BankRedirectBilling, api_models::payments::ConnectorMetadata, api_models::payments::FeatureMetadata, + api_models::payments::SdkType, api_models::payments::ApplepayConnectorMetadataRequest, api_models::payments::SessionTokenInfo, api_models::payments::PaymentProcessingDetailsAt, diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 0ecbbf63e3e..36daa62e558 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1538,7 +1538,7 @@ pub struct Sdk { /// /// This field is required for requests where deviceChannel = 01 (APP). /// Available for supporting EMV 3DS 2.3.1 and later versions. - sdk_type: Option, + sdk_type: Option, /// Indicates the characteristics of a Default-SDK. /// @@ -1563,16 +1563,35 @@ impl From for Sdk { sdk_reference_number: Some(sdk_info.sdk_reference_number), sdk_trans_id: Some(sdk_info.sdk_trans_id), sdk_server_signed_content: None, - sdk_type: None, - default_sdk_type: None, + sdk_type: sdk_info + .sdk_type + .map(SdkType::from) + .or(Some(SdkType::DefaultSdk)), + default_sdk_type: Some(DefaultSdkType { + // hardcoding this value because, it's the only value that is accepted + sdk_variant: "01".to_string(), + wrapped_ind: None, + }), split_sdk_type: None, } } } +impl From for SdkType { + fn from(sdk_type: api_models::payments::SdkType) -> Self { + match sdk_type { + api_models::payments::SdkType::DefaultSdk => Self::DefaultSdk, + api_models::payments::SdkType::SplitSdk => Self::SplitSdk, + api_models::payments::SdkType::LimitedSdk => Self::LimitedSdk, + api_models::payments::SdkType::BrowserSdk => Self::BrowserSdk, + api_models::payments::SdkType::ShellSdk => Self::ShellSdk, + } + } +} + /// Enum representing the type of 3DS SDK. #[derive(Serialize, Deserialize, Debug, Clone)] -pub enum SdkTypeEnum { +pub enum SdkType { #[serde(rename = "01")] DefaultSdk, #[serde(rename = "02")] From ae39374c6b41635e6c474b429fd1df59d30aa6dd Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:10:24 +0530 Subject: [PATCH 018/133] feat(router): add core changes for external authentication flow through unified_authentication_service (#7063) Co-authored-by: Sahkal Poddar Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Sahkal Poddar --- .../unified_authentication_service.rs | 24 +- .../transformers.rs | 28 +- .../src/default_implementations.rs | 82 ++++- .../unified_authentication_service.rs | 3 + .../unified_authentication_service.rs | 80 +++- crates/hyperswitch_domain_models/src/types.rs | 16 +- crates/hyperswitch_interfaces/src/api.rs | 29 +- crates/hyperswitch_interfaces/src/types.rs | 13 +- crates/router/src/core/payments.rs | 109 ++++-- .../connector_integration_v2_impls.rs | 16 +- crates/router/src/core/payments/flows.rs | 63 +++- crates/router/src/core/payments/helpers.rs | 67 ++++ crates/router/src/core/payments/operations.rs | 1 + .../payments/operations/payment_confirm.rs | 216 ++++++++--- .../core/unified_authentication_service.rs | 344 ++++++++++++++++-- .../transformers.rs | 45 --- .../unified_authentication_service/types.rs | 125 ++++++- .../unified_authentication_service/utils.rs | 177 ++++++--- crates/router/src/routes/payments.rs | 9 +- crates/router/src/types.rs | 4 +- 20 files changed, 1190 insertions(+), 261 deletions(-) delete mode 100644 crates/router/src/core/unified_authentication_service/transformers.rs diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs index ece7130bb1d..55f3528bbc4 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs @@ -13,12 +13,12 @@ use hyperswitch_domain_models::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, refunds::{Execute, RSync}, - PostAuthenticate, PreAuthenticate, + Authenticate, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, @@ -71,6 +71,7 @@ impl api::PaymentToken for UnifiedAuthenticationService {} impl api::UnifiedAuthenticationService for UnifiedAuthenticationService {} impl api::UasPreAuthentication for UnifiedAuthenticationService {} impl api::UasPostAuthentication for UnifiedAuthenticationService {} +impl api::UasAuthentication for UnifiedAuthenticationService {} impl ConnectorIntegration for UnifiedAuthenticationService @@ -209,8 +210,16 @@ impl )?; let amount = utils::convert_amount( self.amount_converter, - transaction_details.amount, - transaction_details.currency, + transaction_details + .amount + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "amount", + })?, + transaction_details + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, )?; let connector_router_data = @@ -366,6 +375,11 @@ impl } } +impl ConnectorIntegration + for UnifiedAuthenticationService +{ +} + impl ConnectorIntegration for UnifiedAuthenticationService { diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs index 9af21164d06..f2ca77d746f 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs @@ -3,7 +3,8 @@ use common_utils::types::FloatMajorUnit; use hyperswitch_domain_models::{ router_data::{ConnectorAuthType, RouterData}, router_request_types::unified_authentication_service::{ - DynamicData, PostAuthenticationDetails, TokenDetails, UasAuthenticationResponseData, + DynamicData, PostAuthenticationDetails, PreAuthenticationDetails, TokenDetails, + UasAuthenticationResponseData, }, types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, }; @@ -238,7 +239,10 @@ impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasPreAuthenticationRouter .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "transaction_details", })? - .currency, + .currency + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "currency", + })?, date: None, pan_source: None, protection_type: None, @@ -301,7 +305,18 @@ impl >, ) -> Result { Ok(Self { - response: Ok(UasAuthenticationResponseData::PreAuthentication {}), + response: Ok(UasAuthenticationResponseData::PreAuthentication { + authentication_details: PreAuthenticationDetails { + threeds_server_transaction_id: None, + maximum_supported_3ds_version: None, + connector_authentication_id: None, + three_ds_method_data: None, + three_ds_method_url: None, + message_version: None, + connector_metadata: None, + directory_server_id: None, + }, + }), ..item.data }) } @@ -337,7 +352,7 @@ pub struct UasTokenDetails { #[derive(Serialize, Deserialize, Debug, Clone)] pub struct UasDynamicData { pub dynamic_data_value: Option>, - pub dynamic_data_type: String, + pub dynamic_data_type: Option, pub ds_trans_id: Option, } @@ -384,7 +399,7 @@ impl response: Ok(UasAuthenticationResponseData::PostAuthentication { authentication_details: PostAuthenticationDetails { eci: item.response.authentication_details.eci, - token_details: TokenDetails { + token_details: Some(TokenDetails { payment_token: item .response .authentication_details @@ -405,7 +420,7 @@ impl .authentication_details .token_details .token_expiration_year, - }, + }), dynamic_data_details: item .response .authentication_details @@ -415,6 +430,7 @@ impl dynamic_data_type: dynamic_data.dynamic_data_type, ds_trans_id: dynamic_data.ds_trans_id, }), + trans_status: None, }, }), ..item.data diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index e70aff630e3..f58a4802d50 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -33,12 +33,12 @@ use hyperswitch_domain_models::{ PreProcessing, Reject, SdkSessionUpdate, }, webhooks::VerifyWebhookSource, - PostAuthenticate, PreAuthenticate, + Authenticate, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, @@ -74,7 +74,7 @@ use hyperswitch_interfaces::{ PaymentSessionUpdate, PaymentsCompleteAuthorize, PaymentsPostProcessing, PaymentsPreProcessing, TaxCalculation, }, - ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, + ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, UasAuthentication, UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, }, errors::ConnectorError, @@ -2648,3 +2648,77 @@ default_imp_for_uas_post_authentication!( connectors::Zen, connectors::Zsl ); + +macro_rules! default_imp_for_uas_authentication { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthentication for $path::$connector {} + impl + ConnectorIntegration< + Authenticate, + UasAuthenticationRequestData, + UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_authentication!( + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Billwerk, + connectors::Bitpay, + connectors::Bluesnap, + connectors::Boku, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Coinbase, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Cybersource, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Inespay, + connectors::Jpmorgan, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Payeezy, + connectors::Payu, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mollie, + connectors::Multisafepay, + connectors::Paybox, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, + connectors::Taxjar, + connectors::Thunes, + connectors::Tsys, + connectors::Wellsfargo, + connectors::Worldline, + connectors::Worldpay, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl +); diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs index 329c18b741e..ddc56eb2a29 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs @@ -3,3 +3,6 @@ pub struct PreAuthenticate; #[derive(Debug, Clone)] pub struct PostAuthenticate; + +#[derive(Debug, Clone)] +pub struct Authenticate; diff --git a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs index af2a940cb51..4238c513f44 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs @@ -1,14 +1,47 @@ +use api_models::payments::DeviceChannel; use masking::Secret; -#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] +use crate::{address::Address, payment_method_data::PaymentMethodData}; + +#[derive(Clone, Debug)] pub struct UasPreAuthenticationRequestData { pub service_details: Option, pub transaction_details: Option, + pub payment_details: Option, } -#[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] +#[derive(Clone, Debug)] +pub struct UasAuthenticationRequestData { + pub payment_method_data: PaymentMethodData, + pub billing_address: Address, + pub shipping_address: Option
, + pub browser_details: Option, + pub transaction_details: TransactionDetails, + pub pre_authentication_data: super::authentication::PreAuthenticationData, + pub return_url: Option, + pub sdk_information: Option, + pub email: Option, + pub threeds_method_comp_ind: api_models::payments::ThreeDsCompletionIndicator, + pub three_ds_requestor_url: String, + pub webhook_url: String, +} + +#[derive(Clone, Debug)] pub struct CtpServiceDetails { pub service_session_ids: Option, + pub payment_details: Option, +} + +#[derive(Debug, Clone)] +pub struct PaymentDetails { + pub pan: cards::CardNumber, + pub digital_card_id: Option, + pub payment_data_type: Option, + pub encrypted_src_card_details: Option, + pub card_expiry_date: Secret, + pub cardholder_name: Option>, + pub card_token_number: Secret, + pub account_type: Option, } #[derive(Clone, serde::Deserialize, Debug, serde::Serialize)] @@ -20,26 +53,57 @@ pub struct ServiceSessionIds { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct TransactionDetails { - pub amount: common_utils::types::MinorUnit, - pub currency: common_enums::Currency, + pub amount: Option, + pub currency: Option, + pub device_channel: Option, + pub message_category: Option, } #[derive(Clone, Debug)] -pub struct UasPostAuthenticationRequestData {} +pub struct UasPostAuthenticationRequestData { + pub threeds_server_transaction_id: Option, +} #[derive(Debug, Clone)] pub enum UasAuthenticationResponseData { - PreAuthentication {}, + PreAuthentication { + authentication_details: PreAuthenticationDetails, + }, + Authentication { + authentication_details: AuthenticationDetails, + }, PostAuthentication { authentication_details: PostAuthenticationDetails, }, } +#[derive(Debug, Clone)] +pub struct PreAuthenticationDetails { + pub threeds_server_transaction_id: Option, + pub maximum_supported_3ds_version: Option, + pub connector_authentication_id: Option, + pub three_ds_method_data: Option, + pub three_ds_method_url: Option, + pub message_version: Option, + pub connector_metadata: Option, + pub directory_server_id: Option, +} + +#[derive(Debug, Clone)] +pub struct AuthenticationDetails { + pub authn_flow_type: super::authentication::AuthNFlowType, + pub authentication_value: Option, + pub trans_status: common_enums::TransactionStatus, + pub connector_metadata: Option, + pub ds_trans_id: Option, +} + #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct PostAuthenticationDetails { pub eci: Option, - pub token_details: TokenDetails, + pub token_details: Option, pub dynamic_data_details: Option, + pub trans_status: Option, } #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] @@ -53,6 +117,6 @@ pub struct TokenDetails { #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct DynamicData { pub dynamic_data_value: Option>, - pub dynamic_data_type: String, + pub dynamic_data_type: Option, pub ds_trans_id: Option, } diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index 4bbab24edfe..dc64560ead6 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -3,15 +3,15 @@ pub use diesel_models::types::OrderDetailsWithAmount; use crate::{ router_data::{AccessToken, RouterData}, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, Authorize, AuthorizeSessionToken, - CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, Execute, - IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, PostSessionTokens, - PreAuthenticate, PreProcessing, RSync, Session, SetupMandate, Void, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, Authorize, + AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, + Execute, IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, + PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, SetupMandate, Void, }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, MandateRevokeRequestData, PaymentMethodTokenizationData, @@ -57,7 +57,6 @@ pub type PaymentsSessionRouterData = RouterData; - pub type UasPreAuthenticationRouterData = RouterData; @@ -71,3 +70,6 @@ pub type PaymentsIncrementalAuthorizationRouterData = RouterData< #[cfg(feature = "payouts")] pub type PayoutsRouterData = RouterData; + +pub type UasAuthenticationRouterData = + RouterData; diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index ca2d806762a..ab918b5334b 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -34,13 +34,13 @@ use hyperswitch_domain_models::{ UasFlowData, }, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, PostAuthenticate, PreAuthenticate, - VerifyWebhookSource, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, PostAuthenticate, + PreAuthenticate, VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AccessTokenRequestData, MandateRevokeRequestData, VerifyWebhookSourceRequestData, }, @@ -371,7 +371,7 @@ pub trait ConnectorVerifyWebhookSourceV2: /// trait UnifiedAuthenticationService pub trait UnifiedAuthenticationService: - ConnectorCommon + UasPreAuthentication + UasPostAuthentication + ConnectorCommon + UasPreAuthentication + UasPostAuthentication + UasAuthentication { } @@ -395,9 +395,15 @@ pub trait UasPostAuthentication: { } +/// trait UasAuthentication +pub trait UasAuthentication: + ConnectorIntegration +{ +} + /// trait UnifiedAuthenticationServiceV2 pub trait UnifiedAuthenticationServiceV2: - ConnectorCommon + UasPreAuthenticationV2 + UasPostAuthenticationV2 + ConnectorCommon + UasPreAuthenticationV2 + UasPostAuthenticationV2 + UasAuthenticationV2 { } @@ -423,6 +429,17 @@ pub trait UasPostAuthenticationV2: { } +/// trait UasAuthenticationV2 +pub trait UasAuthenticationV2: + ConnectorIntegrationV2< + Authenticate, + UasFlowData, + UasAuthenticationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait ConnectorValidation pub trait ConnectorValidation: ConnectorCommon + ConnectorSpecifications { /// Validate, the payment request against the connector supported features diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index eb31fd221d6..a047e7f00d7 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -14,13 +14,13 @@ use hyperswitch_domain_models::{ Session, SetupMandate, Void, }, refunds::{Execute, RSync}, - unified_authentication_service::{PostAuthenticate, PreAuthenticate}, + unified_authentication_service::{Authenticate, PostAuthenticate, PreAuthenticate}, webhooks::VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, @@ -205,3 +205,10 @@ pub type UasPostAuthenticationType = dyn ConnectorIntegration< UasPostAuthenticationRequestData, UasAuthenticationResponseData, >; + +/// Type alias for `ConnectorIntegration` +pub type UasAuthenticationType = dyn ConnectorIntegration< + Authenticate, + UasAuthenticationRequestData, + UasAuthenticationResponseData, +>; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e6cd8409343..e991593b4b5 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -393,6 +393,7 @@ where &connector_details, &business_profile, &key_store, + mandate_type, ) .await?; } else { @@ -6407,12 +6408,17 @@ pub async fn payment_external_authentication( #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument(skip_all)] -pub async fn payment_external_authentication( +pub async fn payment_external_authentication( state: SessionState, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, req: api_models::payments::PaymentsExternalAuthenticationRequest, ) -> RouterResponse { + use super::unified_authentication_service::types::ExternalAuthentication; + use crate::core::unified_authentication_service::{ + types::UnifiedAuthenticationService, utils::external_authentication_update_trackers, + }; + let db = &*state.store; let key_manager_state = &(&state).into(); @@ -6584,35 +6590,78 @@ pub async fn payment_external_authentication( .get_required_value("authentication_connector_details") .attach_printable("authentication_connector_details not configured by the merchant")?; - let authentication_response = Box::pin(authentication_core::perform_authentication( - &state, - business_profile.merchant_id, - authentication_connector, - payment_method_details.0, - payment_method_details.1, - billing_address - .as_ref() - .map(|address| address.into()) - .ok_or(errors::ApiErrorResponse::MissingRequiredField { - field_name: "billing_address", - })?, - shipping_address.as_ref().map(|address| address.into()), - browser_info, - merchant_connector_account, - Some(amount), - Some(currency), - authentication::MessageCategory::Payment, - req.device_channel, - authentication, - return_url, - req.sdk_information, - req.threeds_method_comp_ind, - optional_customer.and_then(|customer| customer.email.map(pii::Email::from)), - webhook_url, - authentication_details.three_ds_requestor_url.clone(), - payment_intent.psd2_sca_exemption_type, - )) - .await?; + let authentication_response = + if helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), &state) + .await? + { + let auth_response = + >::authentication( + &state, + &business_profile, + payment_method_details.1, + payment_method_details.0, + billing_address + .as_ref() + .map(|address| address.into()) + .ok_or(errors::ApiErrorResponse::MissingRequiredField { + field_name: "billing_address", + })?, + shipping_address.as_ref().map(|address| address.into()), + browser_info, + Some(amount), + Some(currency), + authentication::MessageCategory::Payment, + req.device_channel, + authentication.clone(), + return_url, + req.sdk_information, + req.threeds_method_comp_ind, + optional_customer.and_then(|customer| customer.email.map(pii::Email::from)), + webhook_url, + authentication_details.three_ds_requestor_url.clone(), + &merchant_connector_account, + &authentication_connector, + ) + .await?; + let authentication = external_authentication_update_trackers( + &state, + auth_response, + authentication.clone(), + None, + ) + .await?; + authentication::AuthenticationResponse::try_from(authentication)? + } else { + Box::pin(authentication_core::perform_authentication( + &state, + business_profile.merchant_id, + authentication_connector, + payment_method_details.0, + payment_method_details.1, + billing_address + .as_ref() + .map(|address| address.into()) + .ok_or(errors::ApiErrorResponse::MissingRequiredField { + field_name: "billing_address", + })?, + shipping_address.as_ref().map(|address| address.into()), + browser_info, + merchant_connector_account, + Some(amount), + Some(currency), + authentication::MessageCategory::Payment, + req.device_channel, + authentication, + return_url, + req.sdk_information, + req.threeds_method_comp_ind, + optional_customer.and_then(|customer| customer.email.map(pii::Email::from)), + webhook_url, + authentication_details.three_ds_requestor_url.clone(), + payment_intent.psd2_sca_exemption_type, + )) + .await? + }; Ok(services::ApplicationResponse::Json( api_models::payments::PaymentsExternalAuthenticationResponse { transaction_status: authentication_response.trans_status, diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 03b1ad0340b..5263ec9fb28 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1,6 +1,9 @@ -use hyperswitch_domain_models::router_flow_types::{PostAuthenticate, PreAuthenticate}; +use hyperswitch_domain_models::router_flow_types::{ + Authenticate, PostAuthenticate, PreAuthenticate, +}; use hyperswitch_interfaces::api::{ - UasPostAuthenticationV2, UasPreAuthenticationV2, UnifiedAuthenticationServiceV2, + UasAuthenticationV2, UasPostAuthenticationV2, UasPreAuthenticationV2, + UnifiedAuthenticationServiceV2, }; #[cfg(feature = "frm")] @@ -2076,6 +2079,7 @@ macro_rules! default_imp_for_new_connector_integration_uas { $( impl UnifiedAuthenticationServiceV2 for $path::$connector {} impl UasPreAuthenticationV2 for $path::$connector {} impl UasPostAuthenticationV2 for $path::$connector {} + impl UasAuthenticationV2 for $path::$connector {} impl services::ConnectorIntegrationV2< PreAuthenticate, @@ -2092,6 +2096,14 @@ macro_rules! default_imp_for_new_connector_integration_uas { types::UasAuthenticationResponseData, > for $path::$connector {} + impl + services::ConnectorIntegrationV2< + Authenticate, + types::UasFlowData, + types::UasAuthenticationRequestData, + types::UasAuthenticationResponseData, + > for $path::$connector + {} )* }; } diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 459d8a50a37..0c80c5a70e2 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -14,11 +14,12 @@ pub mod setup_mandate_flow; use async_trait::async_trait; use hyperswitch_domain_models::{ mandates::CustomerAcceptance, - router_flow_types::{PostAuthenticate, PreAuthenticate}, + router_flow_types::{Authenticate, PostAuthenticate, PreAuthenticate}, router_request_types::PaymentsCaptureData, }; use hyperswitch_interfaces::api::{ - payouts::Payouts, UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, + payouts::Payouts, UasAuthentication, UasPostAuthentication, UasPreAuthentication, + UnifiedAuthenticationService, }; #[cfg(feature = "frm")] @@ -2529,6 +2530,64 @@ default_imp_for_uas_post_authentication!( connector::Wellsfargopayout, connector::Wise ); + +macro_rules! default_imp_for_uas_authentication { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthentication for $path::$connector {} + impl + services::ConnectorIntegration< + Authenticate, + types::UasAuthenticationRequestData, + types::UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} +#[cfg(feature = "dummy_connector")] +impl UasAuthentication for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + Authenticate, + types::UasAuthenticationRequestData, + types::UasAuthenticationResponseData, + > for connector::DummyConnector +{ +} + +default_imp_for_uas_authentication!( + connector::Adyenplatform, + connector::Aci, + connector::Adyen, + connector::Authorizedotnet, + connector::Braintree, + connector::Checkout, + connector::Ebanx, + connector::Globalpay, + connector::Gpayments, + connector::Iatapay, + connector::Itaubank, + connector::Klarna, + connector::Mifinity, + connector::Netcetera, + connector::Nmi, + connector::Noon, + connector::Nuvei, + connector::Opayo, + connector::Opennode, + connector::Payme, + connector::Payone, + connector::Paypal, + connector::Plaid, + connector::Riskified, + connector::Signifyd, + connector::Stripe, + connector::Threedsecureio, + connector::Trustpay, + connector::Wellsfargopayout, + connector::Wise +); /// Determines whether a capture API call should be made for a payment attempt /// This function evaluates whether an authorized payment should proceed with a capture API call /// based on various payment parameters. It's primarily used in two-step (auth + capture) payment flows for CaptureMethod SequentialAutomatic diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 797aadbbc05..6e85db98430 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -5971,6 +5971,73 @@ pub fn validate_mandate_data_and_future_usage( } } +#[derive(Debug, Clone)] +pub enum UnifiedAuthenticationServiceFlow { + ClickToPayInitiate, + ExternalAuthenticationInitiate { + acquirer_details: authentication::types::AcquirerDetails, + card_number: ::cards::CardNumber, + token: String, + }, + ExternalAuthenticationPostAuthenticate { + authentication_id: String, + }, +} + +#[cfg(feature = "v1")] +pub async fn decide_action_for_unified_authentication_service( + state: &SessionState, + key_store: &domain::MerchantKeyStore, + business_profile: &domain::Profile, + payment_data: &mut PaymentData, + connector_call_type: &api::ConnectorCallType, + mandate_type: Option, +) -> RouterResult> { + let external_authentication_flow = get_payment_external_authentication_flow_during_confirm( + state, + key_store, + business_profile, + payment_data, + connector_call_type, + mandate_type, + ) + .await?; + Ok(match external_authentication_flow { + Some(PaymentExternalAuthenticationFlow::PreAuthenticationFlow { + acquirer_details, + card_number, + token, + }) => Some( + UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { + acquirer_details, + card_number, + token, + }, + ), + Some(PaymentExternalAuthenticationFlow::PostAuthenticationFlow { authentication_id }) => { + Some( + UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate { + authentication_id, + }, + ) + } + None => { + if let Some(payment_method) = payment_data.payment_attempt.payment_method { + if payment_method == storage_enums::PaymentMethod::Card + && business_profile.is_click_to_pay_enabled + && payment_data.service_details.is_some() + { + Some(UnifiedAuthenticationServiceFlow::ClickToPayInitiate) + } else { + None + } + } else { + None + } + } + }) +} + pub enum PaymentExternalAuthenticationFlow { PreAuthenticationFlow { acquirer_details: authentication::types::AcquirerDetails, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 0a264f24e93..911530ba8eb 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -309,6 +309,7 @@ pub trait Domain: Send + Sync { _connector_call_type: &ConnectorCallType, _merchant_account: &domain::Profile, _key_store: &domain::MerchantKeyStore, + _mandate_type: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index da68ee27fd9..edcd798ba99 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1042,47 +1042,55 @@ impl Domain> for &'a self, state: &SessionState, payment_data: &mut PaymentData, - _should_continue_confirm_transaction: &mut bool, - _connector_call_type: &ConnectorCallType, + should_continue_confirm_transaction: &mut bool, + connector_call_type: &ConnectorCallType, business_profile: &domain::Profile, key_store: &domain::MerchantKeyStore, + mandate_type: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { - let authentication_product_ids = business_profile + let unified_authentication_service_flow = + helpers::decide_action_for_unified_authentication_service( + state, + key_store, + business_profile, + payment_data, + connector_call_type, + mandate_type, + ) + .await?; + if let Some(unified_authentication_service_flow) = unified_authentication_service_flow { + match unified_authentication_service_flow { + helpers::UnifiedAuthenticationServiceFlow::ClickToPayInitiate => { + let authentication_product_ids = business_profile .authentication_product_ids .clone() .ok_or(errors::ApiErrorResponse::PreconditionFailed { message: "authentication_product_ids is not configured in business profile" .to_string(), })?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "authentication_product_ids", + })?; - if let Some(payment_method) = payment_data.payment_attempt.payment_method { - if payment_method == storage_enums::PaymentMethod::Card - && business_profile.is_click_to_pay_enabled - && payment_data.service_details.is_some() - { - let click_to_pay_mca_id = authentication_product_ids - .get_click_to_pay_connector_account_id() - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "authentication_product_ids", - })?; - - let key_manager_state = &(state).into(); - let merchant_id = &business_profile.merchant_id; + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; - let connector_mca = state - .store - .find_by_merchant_connector_account_merchant_id_merchant_connector_id( - key_manager_state, - merchant_id, - &click_to_pay_mca_id, - key_store, - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::MerchantConnectorAccountNotFound { - id: click_to_pay_mca_id.get_string_repr().to_string(), - }, - )?; + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; let authentication_id = common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); @@ -1098,7 +1106,7 @@ impl Domain> for key_store, business_profile, payment_data, - &connector_mca, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), &connector_mca.connector_name, &authentication_id, payment_method, @@ -1112,9 +1120,10 @@ impl Domain> for key_store, business_profile, payment_data, - &connector_mca, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), &connector_mca.connector_name, payment_method, + None, ) .await?; @@ -1122,14 +1131,14 @@ impl Domain> for Ok(unified_authentication_service::UasAuthenticationResponseData::PostAuthentication { authentication_details, }) => { + let token_details = authentication_details.token_details.ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Missing authentication_details.token_details")?; (Some( hyperswitch_domain_models::payment_method_data::NetworkTokenData { - token_number: authentication_details.token_details.payment_token, - token_exp_month: authentication_details - .token_details + token_number: token_details.payment_token, + token_exp_month: token_details .token_expiration_month, - token_exp_year: authentication_details - .token_details + token_exp_year: token_details .token_expiration_year, token_cryptogram: authentication_details .dynamic_data_details @@ -1143,8 +1152,10 @@ impl Domain> for eci: authentication_details.eci, }),common_enums::AuthenticationStatus::Success) }, - Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication {}) => (None, common_enums::AuthenticationStatus::Started), - Err(_) => (None, common_enums::AuthenticationStatus::Failed) + Ok(unified_authentication_service::UasAuthenticationResponseData::Authentication { .. }) + | Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication { .. }) + => Err(errors::ApiErrorResponse::InternalServerError).attach_printable("unexpected response received")?, + Err(_) => (None, common_enums::AuthenticationStatus::Failed), }; payment_data.payment_attempt.payment_method = @@ -1166,14 +1177,127 @@ impl Domain> for authentication_status, ) .await?; - } - } - logger::info!( - payment_method=?payment_data.payment_attempt.payment_method, - click_to_pay_enabled=?business_profile.is_click_to_pay_enabled, - "skipping unified authentication service call since payment conditions are not satisfied" - ); + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { + acquirer_details, + token, + .. + } => { + let (authentication_connector, three_ds_connector_account) = + authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; + let authentication_connector_name = authentication_connector.to_string(); + let authentication = authentication::utils::create_new_authentication( + state, + business_profile.merchant_id.clone(), + authentication_connector_name.clone(), + token, + business_profile.get_id().to_owned(), + Some(payment_data.payment_intent.payment_id.clone()), + three_ds_connector_account + .get_mca_id() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while finding mca_id from merchant_connector_account")?, + ) + .await?; + + let pre_auth_response = uas_utils::types::ExternalAuthentication::pre_authentication( + state, + key_store, + business_profile, + payment_data, + &three_ds_connector_account, + &authentication_connector_name, + &authentication.authentication_id, + payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::InternalServerError + ).attach_printable("payment_method not found in payment_attempt")?, + ).await?; + let updated_authentication = uas_utils::utils::external_authentication_update_trackers( + state, + pre_auth_response, + authentication.clone(), + Some(acquirer_details), + ).await?; + payment_data.authentication = Some(updated_authentication.clone()); + if updated_authentication.is_separate_authn_required() + || updated_authentication.authentication_status.is_failed() + { + *should_continue_confirm_transaction = false; + let default_poll_config = types::PollConfig::default(); + let default_config_str = default_poll_config + .encode_to_string_of_json() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while stringifying default poll config")?; + let poll_config = state + .store + .find_config_by_key_unwrap_or( + &types::PollConfig::get_poll_config_key( + updated_authentication.authentication_connector.clone(), + ), + Some(default_config_str), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("The poll config was not found in the DB")?; + let poll_config: types::PollConfig = poll_config + .config + .parse_struct("PollConfig") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while parsing PollConfig")?; + payment_data.poll_config = Some(poll_config) + } + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {authentication_id} => { + let (authentication_connector, three_ds_connector_account) = + authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; + let is_pull_mechanism_enabled = + utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata( + three_ds_connector_account + .get_metadata() + .map(|metadata| metadata.expose()), + ); + let authentication = state + .store + .find_authentication_by_merchant_id_authentication_id( + &business_profile.merchant_id, + authentication_id.clone(), + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| format!("Error while fetching authentication record with authentication_id {authentication_id}"))?; + let updated_authentication = if !authentication.authentication_status.is_terminal_status() && is_pull_mechanism_enabled { + let post_auth_response = uas_utils::types::ExternalAuthentication::post_authentication( + state, + key_store, + business_profile, + payment_data, + &three_ds_connector_account, + &authentication_connector.to_string(), + payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::InternalServerError + ).attach_printable("payment_method not found in payment_attempt")?, + Some(authentication.clone()), + ).await?; + uas_utils::utils::external_authentication_update_trackers( + state, + post_auth_response, + authentication, + None, + ).await? + } else { + authentication + }; + payment_data.authentication = Some(updated_authentication.clone()); + //If authentication is not successful, skip the payment connector flows and mark the payment as failure + if updated_authentication.authentication_status + != api_models::enums::AuthenticationStatus::Success + { + *should_continue_confirm_transaction = false; + } + }, + } + } Ok(()) } diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index 6e47dcac032..d9c36aeaa3d 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -1,48 +1,97 @@ -pub mod transformers; pub mod types; pub mod utils; -use api_models::payments::CtpServiceDetails; +use api_models::payments; use diesel_models::authentication::{Authentication, AuthenticationNew}; use error_stack::ResultExt; use hyperswitch_domain_models::{ errors::api_error_response::ApiErrorResponse, - router_request_types::unified_authentication_service::{ - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + payment_method_data, + router_request_types::{ + authentication::{MessageCategory, PreAuthenticationData}, + unified_authentication_service::{ + PaymentDetails, ServiceSessionIds, TransactionDetails, UasAuthenticationRequestData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + }, + BrowserInformation, + }, + types::{ + UasAuthenticationRouterData, UasPostAuthenticationRouterData, + UasPreAuthenticationRouterData, }, }; -use super::errors::RouterResult; +use super::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType}; use crate::{ core::{ errors::utils::StorageErrorExt, payments::PaymentData, unified_authentication_service::types::{ - ClickToPay, UnifiedAuthenticationService, UNIFIED_AUTHENTICATION_SERVICE, + ClickToPay, ExternalAuthentication, UnifiedAuthenticationService, + UNIFIED_AUTHENTICATION_SERVICE, }, }, db::domain, routes::SessionState, - types::domain::MerchantConnectorAccount, }; #[cfg(feature = "v1")] #[async_trait::async_trait] impl UnifiedAuthenticationService for ClickToPay { + fn get_pre_authentication_request_data( + payment_data: &PaymentData, + ) -> RouterResult { + let service_details = hyperswitch_domain_models::router_request_types::unified_authentication_service::CtpServiceDetails { + service_session_ids: Some(ServiceSessionIds { + merchant_transaction_id: payment_data + .service_details + .as_ref() + .and_then(|details| details.merchant_transaction_id.clone()), + correlation_id: payment_data + .service_details + .as_ref() + .and_then(|details| details.correlation_id.clone()), + x_src_flow_id: payment_data + .service_details + .as_ref() + .and_then(|details| details.x_src_flow_id.clone()), + }), + payment_details: None, + }; + let currency = payment_data.payment_attempt.currency.ok_or( + ApiErrorResponse::MissingRequiredField { + field_name: "currency", + }, + )?; + + let amount = payment_data.payment_attempt.net_amount.get_order_amount(); + let transaction_details = TransactionDetails { + amount: Some(amount), + currency: Some(currency), + device_channel: None, + message_category: None, + }; + + Ok(UasPreAuthenticationRequestData { + service_details: Some(service_details), + transaction_details: Some(transaction_details), + payment_details: None, + }) + } + async fn pre_authentication( state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, payment_data: &PaymentData, - merchant_connector_account: &MerchantConnectorAccount, + merchant_connector_account: &MerchantConnectorAccountType, connector_name: &str, authentication_id: &str, payment_method: common_enums::PaymentMethod, - ) -> RouterResult<()> { - let pre_authentication_data = - UasPreAuthenticationRequestData::try_from(payment_data.clone())?; + ) -> RouterResult { + let pre_authentication_data = Self::get_pre_authentication_request_data(payment_data)?; - let pre_auth_router_data: hyperswitch_domain_models::types::UasPreAuthenticationRouterData = + let pre_auth_router_data: UasPreAuthenticationRouterData = utils::construct_uas_router_data( state, connector_name.to_string(), @@ -59,9 +108,7 @@ impl UnifiedAuthenticationService for ClickToPay { UNIFIED_AUTHENTICATION_SERVICE.to_string(), pre_auth_router_data, ) - .await?; - - Ok(()) + .await } async fn post_authentication( @@ -69,10 +116,11 @@ impl UnifiedAuthenticationService for ClickToPay { _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, payment_data: &PaymentData, - merchant_connector_account: &MerchantConnectorAccount, + merchant_connector_account: &MerchantConnectorAccountType, connector_name: &str, payment_method: common_enums::PaymentMethod, - ) -> RouterResult { + _authentication: Option, + ) -> RouterResult { let authentication_id = payment_data .payment_attempt .authentication_id @@ -80,34 +128,272 @@ impl UnifiedAuthenticationService for ClickToPay { .ok_or(ApiErrorResponse::InternalServerError) .attach_printable("Missing authentication id in payment attempt")?; - let post_authentication_data = UasPostAuthenticationRequestData {}; + let post_authentication_data = UasPostAuthenticationRequestData { + threeds_server_transaction_id: None, + }; - let post_auth_router_data: hyperswitch_domain_models::types::UasPostAuthenticationRouterData = utils::construct_uas_router_data( + let post_auth_router_data: UasPostAuthenticationRouterData = + utils::construct_uas_router_data( + state, + connector_name.to_string(), + payment_method, + payment_data.payment_attempt.merchant_id.clone(), + None, + post_authentication_data, + merchant_connector_account, + Some(authentication_id.clone()), + )?; + + utils::do_auth_connector_call( + state, + UNIFIED_AUTHENTICATION_SERVICE.to_string(), + post_auth_router_data, + ) + .await + } + + fn confirmation( + _state: &SessionState, + _key_store: &domain::MerchantKeyStore, + _business_profile: &domain::Profile, + _merchant_connector_account: &MerchantConnectorAccountType, + ) -> RouterResult<()> { + Ok(()) + } +} + +#[cfg(feature = "v1")] +#[async_trait::async_trait] +impl UnifiedAuthenticationService for ExternalAuthentication { + fn get_pre_authentication_request_data( + payment_data: &PaymentData, + ) -> RouterResult { + let payment_method_data = payment_data + .payment_method_data + .as_ref() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("payment_data.payment_method_data is missing")?; + let payment_details = + if let payment_method_data::PaymentMethodData::Card(card) = payment_method_data { + Some(PaymentDetails { + pan: card.card_number.clone(), + digital_card_id: None, + payment_data_type: None, + encrypted_src_card_details: None, + card_expiry_date: card.card_exp_year.clone(), + cardholder_name: card.card_holder_name.clone(), + card_token_number: card.card_cvc.clone(), + account_type: card.card_network.clone(), + }) + } else { + None + }; + Ok(UasPreAuthenticationRequestData { + service_details: None, + transaction_details: None, + payment_details, + }) + } + + #[allow(clippy::too_many_arguments)] + async fn pre_authentication( + state: &SessionState, + _key_store: &domain::MerchantKeyStore, + _business_profile: &domain::Profile, + payment_data: &PaymentData, + merchant_connector_account: &MerchantConnectorAccountType, + connector_name: &str, + authentication_id: &str, + payment_method: common_enums::PaymentMethod, + ) -> RouterResult { + let pre_authentication_data = Self::get_pre_authentication_request_data(payment_data)?; + + let pre_auth_router_data: UasPreAuthenticationRouterData = + utils::construct_uas_router_data( + state, + connector_name.to_string(), + payment_method, + payment_data.payment_attempt.merchant_id.clone(), + None, + pre_authentication_data, + merchant_connector_account, + Some(authentication_id.to_owned()), + )?; + + utils::do_auth_connector_call( + state, + UNIFIED_AUTHENTICATION_SERVICE.to_string(), + pre_auth_router_data, + ) + .await + } + + fn get_authentication_request_data( + payment_method_data: domain::PaymentMethodData, + billing_address: hyperswitch_domain_models::address::Address, + shipping_address: Option, + browser_details: Option, + amount: Option, + currency: Option, + message_category: MessageCategory, + device_channel: payments::DeviceChannel, + authentication: Authentication, + return_url: Option, + sdk_information: Option, + threeds_method_comp_ind: payments::ThreeDsCompletionIndicator, + email: Option, + webhook_url: String, + three_ds_requestor_url: String, + ) -> RouterResult { + Ok(UasAuthenticationRequestData { + payment_method_data, + billing_address, + shipping_address, + browser_details, + transaction_details: TransactionDetails { + amount, + currency, + device_channel: Some(device_channel), + message_category: Some(message_category), + }, + pre_authentication_data: PreAuthenticationData { + threeds_server_transaction_id: authentication.threeds_server_transaction_id.ok_or( + ApiErrorResponse::MissingRequiredField { + field_name: "authentication.threeds_server_transaction_id", + }, + )?, + message_version: authentication.message_version.ok_or( + ApiErrorResponse::MissingRequiredField { + field_name: "authentication.message_version", + }, + )?, + acquirer_bin: authentication.acquirer_bin, + acquirer_merchant_id: authentication.acquirer_merchant_id, + acquirer_country_code: authentication.acquirer_country_code, + connector_metadata: authentication.connector_metadata, + }, + return_url, + sdk_information, + email, + threeds_method_comp_ind, + three_ds_requestor_url, + webhook_url, + }) + } + + #[allow(clippy::too_many_arguments)] + async fn authentication( + state: &SessionState, + business_profile: &domain::Profile, + payment_method: common_enums::PaymentMethod, + payment_method_data: domain::PaymentMethodData, + billing_address: hyperswitch_domain_models::address::Address, + shipping_address: Option, + browser_details: Option, + amount: Option, + currency: Option, + message_category: MessageCategory, + device_channel: payments::DeviceChannel, + authentication: Authentication, + return_url: Option, + sdk_information: Option, + threeds_method_comp_ind: payments::ThreeDsCompletionIndicator, + email: Option, + webhook_url: String, + three_ds_requestor_url: String, + merchant_connector_account: &MerchantConnectorAccountType, + connector_name: &str, + ) -> RouterResult { + let authentication_data = + >::get_authentication_request_data( + payment_method_data, + billing_address, + shipping_address, + browser_details, + amount, + currency, + message_category, + device_channel, + authentication.clone(), + return_url, + sdk_information, + threeds_method_comp_ind, + email, + webhook_url, + three_ds_requestor_url, + )?; + let auth_router_data: UasAuthenticationRouterData = utils::construct_uas_router_data( state, connector_name.to_string(), payment_method, - payment_data.payment_attempt.merchant_id.clone(), + business_profile.merchant_id.clone(), None, - post_authentication_data, + authentication_data, merchant_connector_account, - Some(authentication_id.clone()), + Some(authentication.authentication_id.to_owned()), )?; - let response = utils::do_auth_connector_call( + Box::pin(utils::do_auth_connector_call( state, UNIFIED_AUTHENTICATION_SERVICE.to_string(), - post_auth_router_data, - ) - .await?; + auth_router_data, + )) + .await + } + + fn get_post_authentication_request_data( + authentication: Option, + ) -> RouterResult { + Ok(UasPostAuthenticationRequestData { + // authentication.threeds_server_transaction_id is mandatory for post-authentication in ExternalAuthentication + threeds_server_transaction_id: Some( + authentication + .and_then(|auth| auth.threeds_server_transaction_id) + .ok_or(ApiErrorResponse::MissingRequiredField { + field_name: "authentication.threeds_server_transaction_id", + })?, + ), + }) + } + + async fn post_authentication( + state: &SessionState, + _key_store: &domain::MerchantKeyStore, + business_profile: &domain::Profile, + _payment_data: &PaymentData, + merchant_connector_account: &MerchantConnectorAccountType, + connector_name: &str, + payment_method: common_enums::PaymentMethod, + authentication: Option, + ) -> RouterResult { + let authentication_data = + >::get_post_authentication_request_data( + authentication.clone(), + )?; + let auth_router_data: UasPostAuthenticationRouterData = utils::construct_uas_router_data( + state, + connector_name.to_string(), + payment_method, + business_profile.merchant_id.clone(), + None, + authentication_data, + merchant_connector_account, + authentication.map(|auth| auth.authentication_id), + )?; - Ok(response) + utils::do_auth_connector_call( + state, + UNIFIED_AUTHENTICATION_SERVICE.to_string(), + auth_router_data, + ) + .await } fn confirmation( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccount, + _merchant_connector_account: &MerchantConnectorAccountType, ) -> RouterResult<()> { Ok(()) } @@ -122,7 +408,7 @@ pub async fn create_new_authentication( payment_id: Option, merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, authentication_id: &str, - service_details: Option, + service_details: Option, authentication_status: common_enums::AuthenticationStatus, ) -> RouterResult { let service_details_value = service_details diff --git a/crates/router/src/core/unified_authentication_service/transformers.rs b/crates/router/src/core/unified_authentication_service/transformers.rs deleted file mode 100644 index 36b4ad7e420..00000000000 --- a/crates/router/src/core/unified_authentication_service/transformers.rs +++ /dev/null @@ -1,45 +0,0 @@ -use error_stack::Report; -use hyperswitch_domain_models::{ - errors::api_error_response::ApiErrorResponse, - router_request_types::unified_authentication_service::{ - CtpServiceDetails, ServiceSessionIds, TransactionDetails, UasPreAuthenticationRequestData, - }, -}; - -use crate::core::payments::PaymentData; - -#[cfg(feature = "v1")] -impl TryFrom> for UasPreAuthenticationRequestData { - type Error = Report; - fn try_from(payment_data: PaymentData) -> Result { - let service_details = CtpServiceDetails { - service_session_ids: Some(ServiceSessionIds { - merchant_transaction_id: payment_data - .service_details - .as_ref() - .and_then(|details| details.merchant_transaction_id.clone()), - correlation_id: payment_data - .service_details - .as_ref() - .and_then(|details| details.correlation_id.clone()), - x_src_flow_id: payment_data - .service_details - .as_ref() - .and_then(|details| details.x_src_flow_id.clone()), - }), - }; - let currency = payment_data.payment_attempt.currency.ok_or( - ApiErrorResponse::MissingRequiredField { - field_name: "currency", - }, - )?; - - let amount = payment_data.payment_attempt.net_amount.get_order_amount(); - let transaction_details = TransactionDetails { amount, currency }; - - Ok(Self { - service_details: Some(service_details), - transaction_details: Some(transaction_details), - }) - } -} diff --git a/crates/router/src/core/unified_authentication_service/types.rs b/crates/router/src/core/unified_authentication_service/types.rs index a400f62ffc3..bb9abdb1219 100644 --- a/crates/router/src/core/unified_authentication_service/types.rs +++ b/crates/router/src/core/unified_authentication_service/types.rs @@ -1,8 +1,23 @@ +use api_models::payments; +use hyperswitch_domain_models::{ + errors::api_error_response::{self as errors, NotImplementedMessage}, + router_request_types::{ + authentication::MessageCategory, + unified_authentication_service::{ + UasAuthenticationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, + }, + BrowserInformation, + }, +}; + use crate::{ - core::{errors::RouterResult, payments::PaymentData}, + core::{ + errors::RouterResult, + payments::{helpers::MerchantConnectorAccountType, PaymentData}, + }, db::domain, routes::SessionState, - types::domain::MerchantConnectorAccount, }; pub const CTP_MASTERCARD: &str = "ctp_mastercard"; @@ -17,34 +32,128 @@ pub const IRRELEVANT_CONNECTOR_REQUEST_REFERENCE_ID_IN_AUTHENTICATION_FLOW: &str pub struct ClickToPay; +pub struct ExternalAuthentication; + #[async_trait::async_trait] pub trait UnifiedAuthenticationService { + fn get_pre_authentication_request_data( + _payment_data: &PaymentData, + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason( + "get_pre_authentication_request_data".to_string(), + ), + } + .into()) + } + #[allow(clippy::too_many_arguments)] async fn pre_authentication( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, _payment_data: &PaymentData, - _merchant_connector_account: &MerchantConnectorAccount, + _merchant_connector_account: &MerchantConnectorAccountType, _connector_name: &str, _authentication_id: &str, _payment_method: common_enums::PaymentMethod, - ) -> RouterResult<()>; + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason("pre_authentication".to_string()), + } + .into()) + } + + #[allow(clippy::too_many_arguments)] + fn get_authentication_request_data( + _payment_method_data: domain::PaymentMethodData, + _billing_address: hyperswitch_domain_models::address::Address, + _shipping_address: Option, + _browser_details: Option, + _amount: Option, + _currency: Option, + _message_category: MessageCategory, + _device_channel: payments::DeviceChannel, + _authentication: diesel_models::authentication::Authentication, + _return_url: Option, + _sdk_information: Option, + _threeds_method_comp_ind: payments::ThreeDsCompletionIndicator, + _email: Option, + _webhook_url: String, + _three_ds_requestor_url: String, + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason( + "get_pre_authentication_request_data".to_string(), + ), + } + .into()) + } + #[allow(clippy::too_many_arguments)] + async fn authentication( + _state: &SessionState, + _business_profile: &domain::Profile, + _payment_method: common_enums::PaymentMethod, + _payment_method_data: domain::PaymentMethodData, + _billing_address: hyperswitch_domain_models::address::Address, + _shipping_address: Option, + _browser_details: Option, + _amount: Option, + _currency: Option, + _message_category: MessageCategory, + _device_channel: payments::DeviceChannel, + _authentication_data: diesel_models::authentication::Authentication, + _return_url: Option, + _sdk_information: Option, + _threeds_method_comp_ind: payments::ThreeDsCompletionIndicator, + _email: Option, + _webhook_url: String, + _three_ds_requestor_url: String, + _merchant_connector_account: &MerchantConnectorAccountType, + _connector_name: &str, + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason("authentication".to_string()), + } + .into()) + } + + fn get_post_authentication_request_data( + _authentication: Option, + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason("post_authentication".to_string()), + } + .into()) + } + + #[allow(clippy::too_many_arguments)] async fn post_authentication( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, _payment_data: &PaymentData, - _merchant_connector_account: &MerchantConnectorAccount, + _merchant_connector_account: &MerchantConnectorAccountType, _connector_name: &str, _payment_method: common_enums::PaymentMethod, - ) -> RouterResult; + _authentication: Option, + ) -> RouterResult { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason("post_authentication".to_string()), + } + .into()) + } fn confirmation( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccount, - ) -> RouterResult<()>; + _merchant_connector_account: &MerchantConnectorAccountType, + ) -> RouterResult<()> { + Err(errors::ApiErrorResponse::NotImplemented { + message: NotImplementedMessage::Reason("confirmation".to_string()), + } + .into()) + } } diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index c74baa3a0b3..15ab09e3cdc 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use common_enums::enums::PaymentMethod; -use diesel_models::authentication::{Authentication, AuthenticationUpdate}; +use common_utils::ext_traits::ValueExt; use error_stack::ResultExt; use hyperswitch_domain_models::{ errors::api_error_response::ApiErrorResponse, @@ -10,7 +10,6 @@ use hyperswitch_domain_models::{ router_data_v2::UasFlowData, router_request_types::unified_authentication_service::UasAuthenticationResponseData, }; -use masking::ExposeOptionInterface; use super::types::{ IRRELEVANT_ATTEMPT_ID_IN_AUTHENTICATION_FLOW, @@ -22,56 +21,10 @@ use crate::{ payments, }, services::{self, execute_connector_processing_step}, - types::{api, domain::MerchantConnectorAccount}, + types::{api, transformers::ForeignFrom}, SessionState, }; -pub async fn update_trackers( - state: &SessionState, - router_data: RouterData, - authentication: Authentication, -) -> RouterResult { - let authentication_update = match router_data.response { - Ok(response) => match response { - UasAuthenticationResponseData::PreAuthentication {} => { - AuthenticationUpdate::AuthenticationStatusUpdate { - trans_status: common_enums::TransactionStatus::InformationOnly, - authentication_status: common_enums::AuthenticationStatus::Pending, - } - } - UasAuthenticationResponseData::PostAuthentication { - authentication_details, - } => AuthenticationUpdate::PostAuthenticationUpdate { - authentication_status: common_enums::AuthenticationStatus::Success, - trans_status: common_enums::TransactionStatus::Success, - authentication_value: authentication_details - .dynamic_data_details - .and_then(|data| data.dynamic_data_value.expose_option()), - eci: authentication_details.eci, - }, - }, - Err(error) => AuthenticationUpdate::ErrorUpdate { - connector_authentication_id: error.connector_transaction_id, - authentication_status: common_enums::AuthenticationStatus::Failed, - error_message: error - .reason - .map(|reason| format!("message: {}, reason: {}", error.message, reason)) - .or(Some(error.message)), - error_code: Some(error.code), - }, - }; - - state - .store - .update_authentication_by_merchant_id_authentication_id( - authentication, - authentication_update, - ) - .await - .change_context(ApiErrorResponse::InternalServerError) - .attach_printable("Error while updating authentication for uas") -} - pub async fn do_auth_connector_call( state: &SessionState, authentication_connector_name: String, @@ -108,12 +61,14 @@ pub fn construct_uas_router_data( merchant_id: common_utils::id_type::MerchantId, address: Option, request_data: Req, - merchant_connector_account: &MerchantConnectorAccount, + merchant_connector_account: &payments::helpers::MerchantConnectorAccountType, authentication_id: Option, ) -> RouterResult> { let auth_type: ConnectorAuthType = merchant_connector_account .get_connector_account_details() - .change_context(ApiErrorResponse::InternalServerError)?; + .parse_value("ConnectorAuthType") + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error while parsing ConnectorAuthType")?; Ok(RouterData { flow: PhantomData, merchant_id, @@ -131,7 +86,7 @@ pub fn construct_uas_router_data( description: None, address: address.unwrap_or_default(), auth_type: common_enums::AuthenticationType::default(), - connector_meta_data: merchant_connector_account.metadata.clone(), + connector_meta_data: merchant_connector_account.get_metadata().clone(), connector_wallets_details: merchant_connector_account.get_connector_wallets_details(), amount_captured: None, minor_amount_captured: None, @@ -168,3 +123,121 @@ pub fn construct_uas_router_data( psd2_sca_exemption_type: None, }) } + +pub async fn external_authentication_update_trackers( + state: &SessionState, + router_data: RouterData, + authentication: diesel_models::authentication::Authentication, + acquirer_details: Option< + hyperswitch_domain_models::router_request_types::authentication::AcquirerDetails, + >, +) -> RouterResult { + let authentication_update = match router_data.response { + Ok(response) => match response { + UasAuthenticationResponseData::PreAuthentication { + authentication_details, + } => diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate { + threeds_server_transaction_id: authentication_details + .threeds_server_transaction_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing threeds_server_transaction_id in PreAuthentication Details", + )?, + maximum_supported_3ds_version: authentication_details + .maximum_supported_3ds_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing maximum_supported_3ds_version in PreAuthentication Details", + )?, + connector_authentication_id: authentication_details + .connector_authentication_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing connector_authentication_id in PreAuthentication Details", + )?, + three_ds_method_data: authentication_details.three_ds_method_data, + three_ds_method_url: authentication_details.three_ds_method_url, + message_version: authentication_details + .message_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("missing message_version in PreAuthentication Details")?, + connector_metadata: authentication_details.connector_metadata, + authentication_status: common_enums::AuthenticationStatus::Pending, + acquirer_bin: acquirer_details + .as_ref() + .map(|acquirer_details| acquirer_details.acquirer_bin.clone()), + acquirer_merchant_id: acquirer_details + .as_ref() + .map(|acquirer_details| acquirer_details.acquirer_merchant_id.clone()), + acquirer_country_code: acquirer_details + .and_then(|acquirer_details| acquirer_details.acquirer_country_code), + directory_server_id: authentication_details.directory_server_id, + }, + UasAuthenticationResponseData::Authentication { + authentication_details, + } => { + let authentication_status = common_enums::AuthenticationStatus::foreign_from( + authentication_details.trans_status.clone(), + ); + diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate { + authentication_value: authentication_details.authentication_value, + trans_status: authentication_details.trans_status, + acs_url: authentication_details.authn_flow_type.get_acs_url(), + challenge_request: authentication_details + .authn_flow_type + .get_challenge_request(), + acs_reference_number: authentication_details + .authn_flow_type + .get_acs_reference_number(), + acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(), + acs_signed_content: authentication_details + .authn_flow_type + .get_acs_signed_content(), + authentication_type: authentication_details + .authn_flow_type + .get_decoupled_authentication_type(), + authentication_status, + connector_metadata: authentication_details.connector_metadata, + ds_trans_id: authentication_details.ds_trans_id, + } + } + UasAuthenticationResponseData::PostAuthentication { + authentication_details, + } => { + let trans_status = authentication_details + .trans_status + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("missing trans_status in PostAuthentication Details")?; + diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate { + authentication_status: common_enums::AuthenticationStatus::foreign_from( + trans_status.clone(), + ), + trans_status, + authentication_value: authentication_details + .dynamic_data_details + .and_then(|details| details.dynamic_data_value) + .map(masking::ExposeInterface::expose), + eci: authentication_details.eci, + } + } + }, + Err(error) => diesel_models::authentication::AuthenticationUpdate::ErrorUpdate { + connector_authentication_id: error.connector_transaction_id, + authentication_status: common_enums::AuthenticationStatus::Failed, + error_message: error + .reason + .map(|reason| format!("message: {}, reason: {}", error.message, reason)) + .or(Some(error.message)), + error_code: Some(error.code), + }, + }; + state + .store + .update_authentication_by_merchant_id_authentication_id( + authentication, + authentication_update, + ) + .await + .change_context(ApiErrorResponse::InternalServerError) + .attach_printable("Error while updating authentication") +} diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 16468aef74c..c88f61f7c82 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1703,12 +1703,9 @@ pub async fn payments_external_authentication( &req, payload, |state, auth: auth::AuthenticationData, req, _| { - payments::payment_external_authentication( - state, - auth.merchant_account, - auth.key_store, - req, - ) + payments::payment_external_authentication::< + hyperswitch_domain_models::router_flow_types::Authenticate, + >(state, auth.merchant_account, auth.key_store, req) }, &auth::HeaderAuth(auth::PublishableKeyAuth), locking_action, diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 2ae2af92478..5baca7bb55a 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -53,8 +53,8 @@ pub use hyperswitch_domain_models::{ }, router_request_types::{ unified_authentication_service::{ - UasAuthenticationResponseData, UasPostAuthenticationRequestData, - UasPreAuthenticationRequestData, + UasAuthenticationRequestData, UasAuthenticationResponseData, + UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, BrowserInformation, ChargeRefunds, ChargeRefundsOptions, CompleteAuthorizeData, From 04a5e3823671d389bb6370570d7424a9e1d30759 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:38:08 +0530 Subject: [PATCH 019/133] fix(samsung_pay): populate `payment_method_data` in the payment response (#7095) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 17 +++++++++++--- api-reference/openapi_spec.json | 17 +++++++++++--- crates/api_models/src/payment_methods.rs | 2 +- crates/api_models/src/payments.rs | 15 +++++++++---- .../src/payments/additional_info.rs | 2 +- .../src/payment_method_data.rs | 2 +- .../fraud_check/operation/fraud_check_pre.rs | 4 ++-- crates/router/src/core/payments/helpers.rs | 22 ++++++++++++++++++- .../payments/operations/payment_create.rs | 8 +++++++ crates/router/src/types/fraud_check.rs | 2 +- 10 files changed, 74 insertions(+), 17 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 236c0af3a38..b315800b08c 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -21493,8 +21493,7 @@ "type": "object", "required": [ "last4", - "card_network", - "type" + "card_network" ], "properties": { "last4": { @@ -21507,7 +21506,8 @@ }, "type": { "type": "string", - "description": "The type of payment method" + "description": "The type of payment method", + "nullable": true } } }, @@ -21864,6 +21864,17 @@ "$ref": "#/components/schemas/WalletAdditionalDataForCard" } } + }, + { + "type": "object", + "required": [ + "samsung_pay" + ], + "properties": { + "samsung_pay": { + "$ref": "#/components/schemas/WalletAdditionalDataForCard" + } + } } ], "description": "Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets." diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 9b840f53138..0aa2c898fc6 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -26102,8 +26102,7 @@ "type": "object", "required": [ "last4", - "card_network", - "type" + "card_network" ], "properties": { "last4": { @@ -26116,7 +26115,8 @@ }, "type": { "type": "string", - "description": "The type of payment method" + "description": "The type of payment method", + "nullable": true } } }, @@ -26473,6 +26473,17 @@ "$ref": "#/components/schemas/WalletAdditionalDataForCard" } } + }, + { + "type": "object", + "required": [ + "samsung_pay" + ], + "properties": { + "samsung_pay": { + "$ref": "#/components/schemas/WalletAdditionalDataForCard" + } + } } ], "description": "Hyperswitch supports SDK integration with Apple Pay and Google Pay wallets. For other wallets, we integrate with their respective connectors, redirecting the customer to the connector for wallet payments. As a result, we don’t receive any payment method data in the confirm call for payments made through other wallets." diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 82a7abc6ef1..12bb97d9b7d 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -829,7 +829,7 @@ pub struct PaymentMethodDataWalletInfo { pub card_network: String, /// The type of payment method #[serde(rename = "type")] - pub card_type: String, + pub card_type: Option, } impl From for PaymentMethodDataWalletInfo { diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 2c0a09bf8f0..2c8723857a8 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2601,6 +2601,7 @@ pub enum AdditionalPaymentData { Wallet { apple_pay: Option, google_pay: Option, + samsung_pay: Option, }, PayLater { klarna_sdk: Option, @@ -3874,6 +3875,8 @@ pub enum WalletResponseData { ApplePay(Box), #[schema(value_type = WalletAdditionalDataForCard)] GooglePay(Box), + #[schema(value_type = WalletAdditionalDataForCard)] + SamsungPay(Box), } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize, ToSchema)] @@ -5410,8 +5413,9 @@ impl From for PaymentMethodDataResponse { AdditionalPaymentData::Wallet { apple_pay, google_pay, - } => match (apple_pay, google_pay) { - (Some(apple_pay_pm), _) => Self::Wallet(Box::new(WalletResponse { + samsung_pay, + } => match (apple_pay, google_pay, samsung_pay) { + (Some(apple_pay_pm), _, _) => Self::Wallet(Box::new(WalletResponse { details: Some(WalletResponseData::ApplePay(Box::new( additional_info::WalletAdditionalDataForCard { last4: apple_pay_pm @@ -5425,13 +5429,16 @@ impl From for PaymentMethodDataResponse { .rev() .collect::(), card_network: apple_pay_pm.network.clone(), - card_type: apple_pay_pm.pm_type.clone(), + card_type: Some(apple_pay_pm.pm_type.clone()), }, ))), })), - (_, Some(google_pay_pm)) => Self::Wallet(Box::new(WalletResponse { + (_, Some(google_pay_pm), _) => Self::Wallet(Box::new(WalletResponse { details: Some(WalletResponseData::GooglePay(Box::new(google_pay_pm))), })), + (_, _, Some(samsung_pay_pm)) => Self::Wallet(Box::new(WalletResponse { + details: Some(WalletResponseData::SamsungPay(Box::new(samsung_pay_pm))), + })), _ => Self::Wallet(Box::new(WalletResponse { details: None })), }, AdditionalPaymentData::BankRedirect { bank_name, details } => { diff --git a/crates/api_models/src/payments/additional_info.rs b/crates/api_models/src/payments/additional_info.rs index 9e8c910cba7..769e98214fa 100644 --- a/crates/api_models/src/payments/additional_info.rs +++ b/crates/api_models/src/payments/additional_info.rs @@ -219,5 +219,5 @@ pub struct WalletAdditionalDataForCard { pub card_network: String, /// The type of payment method #[serde(rename = "type")] - pub card_type: String, + pub card_type: Option, } diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 8c19a20ef32..dcf46964170 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1718,7 +1718,7 @@ impl From for payment_methods::PaymentMethodDataWalletInfo Self { last4: item.info.card_details, card_network: item.info.card_network, - card_type: item.pm_type, + card_type: Some(item.pm_type), } } } diff --git a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs index 59a703d2b3c..da93a14dc94 100644 --- a/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs +++ b/crates/router/src/core/fraud_check/operation/fraud_check_pre.rs @@ -239,7 +239,7 @@ where connector: router_data.connector, payment_id: router_data.payment_id.clone(), attempt_id: router_data.attempt_id, - request: FrmRequest::Checkout(FraudCheckCheckoutData { + request: FrmRequest::Checkout(Box::new(FraudCheckCheckoutData { amount: router_data.request.amount, order_details: router_data.request.order_details, currency: router_data.request.currency, @@ -247,7 +247,7 @@ where payment_method_data: router_data.request.payment_method_data, email: router_data.request.email, gateway: router_data.request.gateway, - }), + })), response: FrmResponse::Checkout(router_data.response), }) } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 6e85db98430..3833d316be6 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4671,6 +4671,7 @@ pub async fn get_additional_payment_data( pm_type: apple_pay_wallet_data.payment_method.pm_type.clone(), }), google_pay: None, + samsung_pay: None, })) } domain::WalletData::GooglePay(google_pay_pm_data) => { @@ -4679,13 +4680,32 @@ pub async fn get_additional_payment_data( google_pay: Some(payment_additional_types::WalletAdditionalDataForCard { last4: google_pay_pm_data.info.card_details.clone(), card_network: google_pay_pm_data.info.card_network.clone(), - card_type: google_pay_pm_data.pm_type.clone(), + card_type: Some(google_pay_pm_data.pm_type.clone()), + }), + samsung_pay: None, + })) + } + domain::WalletData::SamsungPay(samsung_pay_pm_data) => { + Ok(Some(api_models::payments::AdditionalPaymentData::Wallet { + apple_pay: None, + google_pay: None, + samsung_pay: Some(payment_additional_types::WalletAdditionalDataForCard { + last4: samsung_pay_pm_data + .payment_credential + .card_last_four_digits + .clone(), + card_network: samsung_pay_pm_data + .payment_credential + .card_brand + .to_string(), + card_type: None, }), })) } _ => Ok(Some(api_models::payments::AdditionalPaymentData::Wallet { apple_pay: None, google_pay: None, + samsung_pay: None, })), }, domain::PaymentMethodData::PayLater(_) => Ok(Some( diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 2bb751d4e14..ed5c93489cd 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1178,6 +1178,14 @@ impl PaymentCreate { Some(api_models::payments::AdditionalPaymentData::Wallet { apple_pay: None, google_pay: Some(wallet.into()), + samsung_pay: None, + }) + } + Some(enums::PaymentMethodType::SamsungPay) => { + Some(api_models::payments::AdditionalPaymentData::Wallet { + apple_pay: None, + google_pay: None, + samsung_pay: Some(wallet.into()), }) } _ => None, diff --git a/crates/router/src/types/fraud_check.rs b/crates/router/src/types/fraud_check.rs index a861aca67db..386c8a9cd90 100644 --- a/crates/router/src/types/fraud_check.rs +++ b/crates/router/src/types/fraud_check.rs @@ -29,7 +29,7 @@ pub struct FrmRouterData { #[derive(Debug, Clone)] pub enum FrmRequest { Sale(FraudCheckSaleData), - Checkout(FraudCheckCheckoutData), + Checkout(Box), Transaction(FraudCheckTransactionData), Fulfillment(FraudCheckFulfillmentData), RecordReturn(FraudCheckRecordReturnData), From fa530531f51f8743761b489ac495845abdb70bdf Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 4 Feb 2025 00:31:18 +0000 Subject: [PATCH 020/133] chore(version): 2025.02.04.0 --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ed7f89e30e..3d3abbda850 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,25 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.04.0 + +### Features + +- **router:** Add core changes for external authentication flow through unified_authentication_service ([#7063](https://github.com/juspay/hyperswitch/pull/7063)) ([`ae39374`](https://github.com/juspay/hyperswitch/commit/ae39374c6b41635e6c474b429fd1df59d30aa6dd)) + +### Bug Fixes + +- **connector:** [NETCETERA] add `sdk-type` and `default-sdk-type` in netcetera authentication request ([#7156](https://github.com/juspay/hyperswitch/pull/7156)) ([`64a7afa`](https://github.com/juspay/hyperswitch/commit/64a7afa6d42270d96788119e666b97176cd753dd)) +- **samsung_pay:** Populate `payment_method_data` in the payment response ([#7095](https://github.com/juspay/hyperswitch/pull/7095)) ([`04a5e38`](https://github.com/juspay/hyperswitch/commit/04a5e3823671d389bb6370570d7424a9e1d30759)) + +### Miscellaneous Tasks + +- Bump cypress to `v14.0.0` ([#7102](https://github.com/juspay/hyperswitch/pull/7102)) ([`0e9966a`](https://github.com/juspay/hyperswitch/commit/0e9966a54d87f55b0f5c54e4dccb80742674fe26)) + +**Full Changelog:** [`2025.01.31.0...2025.02.04.0`](https://github.com/juspay/hyperswitch/compare/2025.01.31.0...2025.02.04.0) + +- - - + ## 2025.01.31.0 ### Miscellaneous Tasks From 8ac1b83985dbae33afc3b53d46b85a374ff3c1e9 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:43:37 +0530 Subject: [PATCH 021/133] fix: invalidate surcharge cache during update (#6907) --- crates/router/src/core/surcharge_decision_config.rs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/crates/router/src/core/surcharge_decision_config.rs b/crates/router/src/core/surcharge_decision_config.rs index 0ff5f3fb373..2d817fd1afe 100644 --- a/crates/router/src/core/surcharge_decision_config.rs +++ b/crates/router/src/core/surcharge_decision_config.rs @@ -56,7 +56,7 @@ pub async fn upsert_surcharge_decision_config( message: "Invalid Request Data".to_string(), }) .attach_printable("The Request has an Invalid Comparison")?; - + let surcharge_cache_key = merchant_account.get_id().get_surcharge_dsk_key(); match read_config_key { Ok(config) => { let previous_record: SurchargeDecisionManagerRecord = config @@ -88,7 +88,7 @@ pub async fn upsert_surcharge_decision_config( .attach_printable("Error serializing the config")?; algo_id.update_surcharge_config_id(key.clone()); - let config_key = cache::CacheKind::Surcharge(key.into()); + let config_key = cache::CacheKind::Surcharge(surcharge_cache_key.into()); update_merchant_active_algorithm_ref(&state, &key_store, config_key, algo_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) @@ -125,7 +125,7 @@ pub async fn upsert_surcharge_decision_config( .attach_printable("Error fetching the config")?; algo_id.update_surcharge_config_id(key.clone()); - let config_key = cache::CacheKind::Surcharge(key.clone().into()); + let config_key = cache::CacheKind::Surcharge(surcharge_cache_key.into()); update_merchant_active_algorithm_ref(&state, &key_store, config_key, algo_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) @@ -173,7 +173,8 @@ pub async fn delete_surcharge_decision_config( .attach_printable("Could not decode the surcharge conditional_config algorithm")? .unwrap_or_default(); algo_id.surcharge_config_algo_id = None; - let config_key = cache::CacheKind::Surcharge(key.clone().into()); + let surcharge_cache_key = merchant_account.get_id().get_surcharge_dsk_key(); + let config_key = cache::CacheKind::Surcharge(surcharge_cache_key.into()); update_merchant_active_algorithm_ref(&state, &key_store, config_key, algo_id) .await .change_context(errors::ApiErrorResponse::InternalServerError) From 55bb284ba063dc84e80b4f0d83c82ec7c30ad4c5 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Tue, 4 Feb 2025 01:43:55 +0530 Subject: [PATCH 022/133] fix(router): [Cybersource] add flag to indicate final capture (#7085) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/connectors/cybersource/transformers.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 9f3d0241719..5fc9cad4ffe 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -417,6 +417,7 @@ pub enum CybersourcePaymentInitiatorTypes { pub struct CaptureOptions { capture_sequence_number: u32, total_capture_count: u32, + is_final: Option, } #[derive(Debug, Serialize)] @@ -2142,11 +2143,18 @@ impl TryFrom<&CybersourceRouterData<&PaymentsCaptureRouterData>> .clone() .map(convert_metadata_to_merchant_defined_info); + let is_final = matches!( + item.router_data.request.capture_method, + Some(enums::CaptureMethod::Manual) + ) + .then_some(true); + Ok(Self { processing_information: ProcessingInformation { capture_options: Some(CaptureOptions { capture_sequence_number: 1, total_capture_count: 1, + is_final, }), action_list: None, action_token_types: None, From ed8ef2466b7f059ed0f534aa1f3fca9b5ecbeefd Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:11:54 +0530 Subject: [PATCH 023/133] ci: disable stripe in cypress (#7183) --- .github/workflows/cypress-tests-runner.yml | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index 4de046499ab..67efcdb0ea3 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -13,7 +13,7 @@ concurrency: env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 - PAYMENTS_CONNECTORS: "cybersource stripe" + PAYMENTS_CONNECTORS: "cybersource" PAYOUTS_CONNECTORS: "wise" RUST_BACKTRACE: short RUSTUP_MAX_RETRIES: 10 @@ -50,7 +50,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v4 with: - node-version: '20' + node-version: "20" - name: Install Cypress and dependencies run: | @@ -189,8 +189,7 @@ jobs: if: ${{ env.RUN_TESTS == 'true' }} uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" - name: Install Cypress and dependencies if: ${{ env.RUN_TESTS == 'true' }} @@ -377,8 +376,7 @@ jobs: if: ${{ env.RUN_TESTS == 'true' }} uses: actions/setup-node@v4 with: - node-version: '20' - cache: 'npm' + node-version: "20" - name: Install Cypress and dependencies if: ${{ env.RUN_TESTS == 'true' }} @@ -466,4 +464,4 @@ jobs: with: token: ${{ secrets.CODECOV_TOKEN }} files: ${{ env.CODECOV_FILE }} - disable_search: true \ No newline at end of file + disable_search: true From e2ddcc26b84e4ddcd69005080e19d211b1604827 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Tue, 4 Feb 2025 15:48:04 +0530 Subject: [PATCH 024/133] fix(router): add dynamic fields support for `samsung_pay` (#7090) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 1 + .../payment_connector_required_fields.rs | 21 +++++++++++++++++++ loadtest/config/development.toml | 1 + 8 files changed, 28 insertions(+) diff --git a/config/config.example.toml b/config/config.example.toml index 1d4507126fe..b76905eb534 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -460,6 +460,7 @@ bank_debit.sepa = { connector_list = "gocardless,adyen" } bank_redirect.ideal = { connector_list = "stripe,adyen,globalpay" } # Mandate supported payment method type and connector for bank_redirect bank_redirect.sofort = { connector_list = "stripe,adyen,globalpay" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } +wallet.samsung_pay = { connector_list = "cybersource" } wallet.google_pay = { connector_list = "bankofamerica" } bank_redirect.giropay = { connector_list = "adyen,globalpay" } diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index d606f192e4e..5791c6aa83e 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -169,6 +169,7 @@ card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.samsung_pay.connector_list = "cybersource" wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" wallet.momo.connector_list = "adyen" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index ff5abd3fb03..78590f05f1b 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -169,6 +169,7 @@ card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.samsung_pay.connector_list = "cybersource" wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" wallet.momo.connector_list = "adyen" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 208621433bb..4127a694fcb 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -169,6 +169,7 @@ card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.samsung_pay.connector_list = "cybersource" wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" wallet.momo.connector_list = "adyen" diff --git a/config/development.toml b/config/development.toml index b293151a988..8209d49565f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -628,6 +628,7 @@ card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.samsung_pay.connector_list = "cybersource" wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" wallet.momo.connector_list = "adyen" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 656d4fa7ecb..0d3cbb4f6af 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -522,6 +522,7 @@ adyen = { banks = "aib,bank_of_scotland,danske_bank,first_direct,first_trust,hal pay_later.klarna = { connector_list = "adyen" } wallet.google_pay = { connector_list = "stripe,adyen,bankofamerica" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } +wallet.samsung_pay = { connector_list = "cybersource" } wallet.paypal = { connector_list = "adyen" } card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index d957109bcef..2ef89f440ad 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -53,6 +53,12 @@ impl Default for Mandates { ]), }, ), + ( + enums::PaymentMethodType::SamsungPay, + SupportedConnectorsForMandate { + connector_list: HashSet::from([enums::Connector::Cybersource]), + }, + ), ])), ), ( @@ -8889,6 +8895,21 @@ impl Default for settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::SamsungPay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Cybersource, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), ( enums::PaymentMethodType::GooglePay, ConnectorFields { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index ac90dc88198..4d9c686f23b 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -338,6 +338,7 @@ card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" +wallet.samsung_pay.connector_list = "cybersource" wallet.google_pay.connector_list = "stripe,adyen,cybersource,bankofamerica,noon,globalpay,multisafepay,novalnet" wallet.paypal.connector_list = "adyen,globalpay,nexinets,novalnet,paypal" wallet.momo.connector_list = "adyen" From b9aa3ab445e7966dad3f7c09f27e644d5628f61f Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Tue, 4 Feb 2025 22:43:02 +0530 Subject: [PATCH 025/133] feat(router): add card_discovery in payment_attempt (#7039) Co-authored-by: hrithikesh026 --- crates/common_enums/src/enums.rs | 25 ++++++++++++++++ crates/diesel_models/src/enums.rs | 4 +-- crates/diesel_models/src/payment_attempt.rs | 30 +++++++++++++++++++ crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/schema_v2.rs | 1 + crates/diesel_models/src/user/sample_data.rs | 2 ++ .../src/payments/payment_attempt.rs | 15 ++++++++++ crates/router/src/core/payments.rs | 22 ++++++++++++++ crates/router/src/core/payments/helpers.rs | 1 + .../payments/operations/payment_confirm.rs | 5 +++- .../payments/operations/payment_create.rs | 1 + crates/router/src/core/payments/retry.rs | 1 + .../src/types/storage/payment_attempt.rs | 3 ++ .../src/types/storage/payment_method.rs | 4 +++ crates/router/src/utils/user/sample_data.rs | 1 + .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 5 ++++ .../down.sql | 4 +++ .../up.sql | 4 +++ 19 files changed, 127 insertions(+), 3 deletions(-) create mode 100644 migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/down.sql create mode 100644 migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/up.sql diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 562b4fdf269..c30c9ec9f14 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -181,6 +181,31 @@ impl AttemptStatus { } } +/// Indicates the method by which a card is discovered during a payment +#[derive( + Clone, + Copy, + Debug, + Default, + Hash, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum CardDiscovery { + #[default] + Manual, + SavedCard, + ClickToPay, +} + /// Pass this parameter to force 3DS or non 3DS auth for this payment. Some connectors will still force 3DS auth even in case of passing 'no_three_ds' here and vice versa. Default value is 'no_three_ds' if not set #[derive( Clone, diff --git a/crates/diesel_models/src/enums.rs b/crates/diesel_models/src/enums.rs index ec6e91a2ecb..1ee1a090ffa 100644 --- a/crates/diesel_models/src/enums.rs +++ b/crates/diesel_models/src/enums.rs @@ -4,8 +4,8 @@ pub mod diesel_exports { DbApiVersion as ApiVersion, DbAttemptStatus as AttemptStatus, DbAuthenticationType as AuthenticationType, DbBlocklistDataKind as BlocklistDataKind, DbCaptureMethod as CaptureMethod, DbCaptureStatus as CaptureStatus, - DbConnectorStatus as ConnectorStatus, DbConnectorType as ConnectorType, - DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, + DbCardDiscovery as CardDiscovery, DbConnectorStatus as ConnectorStatus, + DbConnectorType as ConnectorType, DbCountryAlpha2 as CountryAlpha2, DbCurrency as Currency, DbDashboardMetadata as DashboardMetadata, DbDeleteStatus as DeleteStatus, DbDisputeStage as DisputeStage, DbDisputeStatus as DisputeStatus, DbEventClass as EventClass, DbEventObjectType as EventObjectType, DbEventType as EventType, diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index a450a30fa76..dbd867c9e55 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -94,6 +94,7 @@ pub struct PaymentAttempt { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -172,6 +173,7 @@ pub struct PaymentAttempt { pub order_tax_amount: Option, pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -278,6 +280,7 @@ pub struct PaymentAttemptNew { pub payment_method_subtype: storage_enums::PaymentMethodType, pub id: id_type::GlobalAttemptId, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -351,6 +354,7 @@ pub struct PaymentAttemptNew { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -423,6 +427,7 @@ pub enum PaymentAttemptUpdate { shipping_cost: Option, order_tax_amount: Option, connector_mandate_detail: Option, + card_discovery: Option, }, VoidUpdate { status: storage_enums::AttemptStatus, @@ -848,6 +853,7 @@ pub struct PaymentAttemptUpdateInternal { pub order_tax_amount: Option, pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -1031,6 +1037,7 @@ impl PaymentAttemptUpdate { order_tax_amount, connector_transaction_data, connector_mandate_detail, + card_discovery, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1089,6 +1096,7 @@ impl PaymentAttemptUpdate { connector_transaction_data: connector_transaction_data .or(source.connector_transaction_data), connector_mandate_detail: connector_mandate_detail.or(source.connector_mandate_detail), + card_discovery: card_discovery.or(source.card_discovery), ..source } } @@ -2141,6 +2149,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2197,6 +2206,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2232,6 +2242,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost, order_tax_amount, connector_mandate_detail, + card_discovery, } => Self { amount: Some(amount), currency: Some(currency), @@ -2284,6 +2295,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount, connector_transaction_data: None, connector_mandate_detail, + card_discovery, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2341,6 +2353,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2399,6 +2412,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -2457,6 +2471,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::ConnectorMandateDetailUpdate { connector_mandate_detail, @@ -2513,6 +2528,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail, + card_discovery: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -2569,6 +2585,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -2650,6 +2667,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail, + card_discovery: None, } } PaymentAttemptUpdate::ErrorUpdate { @@ -2723,6 +2741,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail: None, + card_discovery: None, } } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { @@ -2777,6 +2796,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -2839,6 +2859,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -2908,6 +2929,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail: None, + card_discovery: None, } } PaymentAttemptUpdate::PreprocessingUpdate { @@ -2976,6 +2998,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail: None, + card_discovery: None, } } PaymentAttemptUpdate::CaptureUpdate { @@ -3034,6 +3057,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -3091,6 +3115,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -3157,6 +3182,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail: None, + card_discovery: None, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3214,6 +3240,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3273,6 +3300,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -3341,6 +3369,7 @@ impl From for PaymentAttemptUpdateInternal { shipping_cost: None, order_tax_amount: None, connector_mandate_detail: None, + card_discovery: None, } } PaymentAttemptUpdate::PostSessionTokensUpdate { @@ -3398,6 +3427,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_transaction_data: None, connector_mandate_detail: None, + card_discovery: None, }, } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 7930ad5d9ae..40343e44365 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -907,6 +907,7 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, connector_mandate_detail -> Nullable, + card_discovery -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 13abf8ee64b..99d6139f310 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -877,6 +877,7 @@ diesel::table! { shipping_cost -> Nullable, order_tax_amount -> Nullable, connector_mandate_detail -> Nullable, + card_discovery -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index cfc9e1c4c8e..4262276c467 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -203,6 +203,7 @@ pub struct PaymentAttemptBatchNew { pub order_tax_amount: Option, pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -282,6 +283,7 @@ impl PaymentAttemptBatchNew { shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, } } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 0a7ada39ecd..74804e658a7 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -402,6 +402,8 @@ pub struct PaymentAttempt { pub id: id_type::GlobalAttemptId, /// The connector mandate details which are stored temporarily pub connector_mandate_detail: Option, + /// Indicates the method by which a card is discovered during a payment + pub card_discovery: Option, } impl PaymentAttempt { @@ -520,6 +522,7 @@ impl PaymentAttempt { error: None, connector_mandate_detail: None, id, + card_discovery: None, }) } } @@ -590,6 +593,7 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -836,6 +840,7 @@ pub struct PaymentAttemptNew { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -902,6 +907,7 @@ pub enum PaymentAttemptUpdate { client_version: Option, customer_acceptance: Option, connector_mandate_detail: Option, + card_discovery: Option, }, RejectUpdate { status: storage_enums::AttemptStatus, @@ -1154,6 +1160,7 @@ impl PaymentAttemptUpdate { client_version, customer_acceptance, connector_mandate_detail, + card_discovery, } => DieselPaymentAttemptUpdate::ConfirmUpdate { amount: net_amount.get_order_amount(), currency, @@ -1188,6 +1195,7 @@ impl PaymentAttemptUpdate { shipping_cost: net_amount.get_shipping_cost(), order_tax_amount: net_amount.get_order_tax_amount(), connector_mandate_detail, + card_discovery, }, Self::VoidUpdate { status, @@ -1551,6 +1559,7 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, }) } @@ -1632,6 +1641,7 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, connector_mandate_detail: storage_model.connector_mandate_detail, + card_discovery: storage_model.card_discovery, }) } .await @@ -1714,6 +1724,7 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, }) } } @@ -1781,6 +1792,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_billing_address, connector, connector_mandate_detail, + card_discovery, } = self; let AttemptAmountDetails { @@ -1858,6 +1870,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_billing_address: payment_method_billing_address.map(Encryption::from), connector_payment_data, connector_mandate_detail, + card_discovery, }) } @@ -1969,6 +1982,7 @@ impl behaviour::Conversion for PaymentAttempt { connector: storage_model.connector, payment_method_billing_address, connector_mandate_detail: storage_model.connector_mandate_detail, + card_discovery: storage_model.card_discovery, }) } .await @@ -2053,6 +2067,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_type_v2: self.payment_method_type, id: self.id, connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, }) } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e991593b4b5..e9d3c013789 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4450,6 +4450,28 @@ pub struct PaymentEvent { } impl PaymentData { + // Get the method by which a card is discovered during a payment + #[cfg(feature = "v1")] + fn get_card_discovery_for_card_payment_method(&self) -> Option { + match self.payment_attempt.payment_method { + Some(storage_enums::PaymentMethod::Card) => { + if self + .token_data + .as_ref() + .map(storage::PaymentTokenData::is_permanent_card) + .unwrap_or(false) + { + Some(common_enums::CardDiscovery::SavedCard) + } else if self.service_details.is_some() { + Some(common_enums::CardDiscovery::ClickToPay) + } else { + Some(common_enums::CardDiscovery::Manual) + } + } + _ => None, + } + } + fn to_event(&self) -> PaymentEvent { PaymentEvent { payment_intent: self.payment_intent.clone(), diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 3833d316be6..0d7a3b57fc1 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4289,6 +4289,7 @@ impl AttemptType { organization_id: old_payment_attempt.organization_id, profile_id: old_payment_attempt.profile_id, connector_mandate_detail: None, + card_discovery: None, } } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index edcd798ba99..9b1c2ea3be3 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1585,7 +1585,7 @@ impl UpdateTracker, api::PaymentsRequest> for let m_payment_token = payment_token.clone(); let m_additional_pm_data = encoded_additional_pm_data .clone() - .or(payment_data.payment_attempt.payment_method_data); + .or(payment_data.payment_attempt.payment_method_data.clone()); let m_business_sub_label = business_sub_label.clone(); let m_straight_through_algorithm = straight_through_algorithm.clone(); let m_error_code = error_code.clone(); @@ -1614,6 +1614,8 @@ impl UpdateTracker, api::PaymentsRequest> for None => (None, None, None), }; + let card_discovery = payment_data.get_card_discovery_for_card_payment_method(); + let payment_attempt_fut = tokio::spawn( async move { m_db.update_payment_attempt_with_attempt_id( @@ -1661,6 +1663,7 @@ impl UpdateTracker, api::PaymentsRequest> for connector_mandate_detail: payment_data .payment_attempt .connector_mandate_detail, + card_discovery, }, storage_scheme, ) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index ed5c93489cd..6ace8c3018f 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1306,6 +1306,7 @@ impl PaymentCreate { organization_id: organization_id.clone(), profile_id, connector_mandate_detail: None, + card_discovery: None, }, additional_pm_data, diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 7ae536a1f19..0401575bc66 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -654,6 +654,7 @@ pub fn make_new_payment_attempt( charge_id: Default::default(), customer_acceptance: Default::default(), connector_mandate_detail: Default::default(), + card_discovery: old_payment_attempt.card_discovery, } } diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 0291374d54f..5958435abc7 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -217,6 +217,7 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + card_discovery: Default::default(), }; let store = state @@ -301,6 +302,7 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + card_discovery: Default::default(), }; let store = state .stores @@ -398,6 +400,7 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + card_discovery: Default::default(), }; let store = state .stores diff --git a/crates/router/src/types/storage/payment_method.rs b/crates/router/src/types/storage/payment_method.rs index bc5f6651b6b..21c15c23b3c 100644 --- a/crates/router/src/types/storage/payment_method.rs +++ b/crates/router/src/types/storage/payment_method.rs @@ -103,6 +103,10 @@ impl PaymentTokenData { pub fn wallet_token(payment_method_id: String) -> Self { Self::WalletToken(WalletTokenData { payment_method_id }) } + + pub fn is_permanent_card(&self) -> bool { + matches!(self, Self::PermanentCard(_) | Self::Permanent(_)) + } } #[cfg(all( diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index bf84dd568a2..c5652e6cbfa 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -361,6 +361,7 @@ pub async fn generate_sample_data( order_tax_amount: None, connector_transaction_data, connector_mandate_detail: None, + card_discovery: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 0415625f7eb..3a45cfad9a2 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -195,6 +195,7 @@ impl PaymentAttemptInterface for MockDb { organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, connector_mandate_detail: payment_attempt.connector_mandate_detail, + card_discovery: payment_attempt.card_discovery, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 06c7fefe85f..3480f8f4ba4 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -564,6 +564,7 @@ impl PaymentAttemptInterface for KVRouterStore { organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), + card_discovery: payment_attempt.card_discovery, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1511,6 +1512,7 @@ impl DataModelExt for PaymentAttempt { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, } } @@ -1587,6 +1589,7 @@ impl DataModelExt for PaymentAttempt { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + card_discovery: storage_model.card_discovery, } } } @@ -1670,6 +1673,7 @@ impl DataModelExt for PaymentAttemptNew { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + card_discovery: self.card_discovery, } } @@ -1742,6 +1746,7 @@ impl DataModelExt for PaymentAttemptNew { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + card_discovery: storage_model.card_discovery, } } } diff --git a/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/down.sql b/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/down.sql new file mode 100644 index 00000000000..9b7be0e960a --- /dev/null +++ b/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/down.sql @@ -0,0 +1,4 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE payment_attempt DROP COLUMN IF EXISTS card_discovery; + +DROP TYPE IF EXISTS "CardDiscovery"; diff --git a/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/up.sql b/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/up.sql new file mode 100644 index 00000000000..657d670bfc8 --- /dev/null +++ b/migrations/2025-01-13-060852_add_card_discovery_in_payment_attempt/up.sql @@ -0,0 +1,4 @@ +-- Your SQL goes here +CREATE TYPE "CardDiscovery" AS ENUM ('manual', 'saved_card', 'click_to_pay'); + +ALTER TABLE payment_attempt ADD COLUMN IF NOT EXISTS card_discovery "CardDiscovery"; From f0b443eda53bfb7b56679277e6077a8d55974763 Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Wed, 5 Feb 2025 00:14:38 +0530 Subject: [PATCH 026/133] fix(connector): [novalnet] Remove first name, last name as required fields for Applepay, Googlepay, Paypal (#7152) --- .../src/connectors/novalnet/transformers.rs | 12 ++--- crates/hyperswitch_connectors/src/utils.rs | 10 ++++ .../payment_connector_required_fields.rs | 54 ------------------- 3 files changed, 16 insertions(+), 60 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 6d3199ecf1a..c1ef6566e03 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -86,8 +86,8 @@ pub struct NovalnetPaymentsRequestBilling { #[derive(Default, Debug, Serialize, Clone)] pub struct NovalnetPaymentsRequestCustomer { - first_name: Secret, - last_name: Secret, + first_name: Option>, + last_name: Option>, email: Email, mobile: Option>, billing: Option, @@ -215,8 +215,8 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym }; let customer = NovalnetPaymentsRequestCustomer { - first_name: item.router_data.get_billing_first_name()?, - last_name: item.router_data.get_billing_last_name()?, + first_name: item.router_data.get_optional_billing_first_name(), + last_name: item.router_data.get_optional_billing_last_name(), email: item .router_data .get_billing_email() @@ -1477,8 +1477,8 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { }; let customer = NovalnetPaymentsRequestCustomer { - first_name: req_address.get_first_name()?.clone(), - last_name: req_address.get_last_name()?.clone(), + first_name: req_address.get_optional_first_name(), + last_name: req_address.get_optional_last_name(), email: item.request.get_email()?.clone(), mobile: item.get_optional_billing_phone_number(), billing: Some(billing), diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index e99777ad27e..1180cb68f7c 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1188,6 +1188,8 @@ pub trait AddressDetailsData { fn get_optional_city(&self) -> Option; fn get_optional_line1(&self) -> Option>; fn get_optional_line2(&self) -> Option>; + fn get_optional_first_name(&self) -> Option>; + fn get_optional_last_name(&self) -> Option>; } impl AddressDetailsData for AddressDetails { @@ -1296,6 +1298,14 @@ impl AddressDetailsData for AddressDetails { fn get_optional_line2(&self) -> Option> { self.line2.clone() } + + fn get_optional_first_name(&self) -> Option> { + self.first_name.clone() + } + + fn get_optional_last_name(&self) -> Option> { + self.last_name.clone() + } } pub trait PhoneDetailsData { diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 2ef89f440ad..644c213acd8 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -8706,24 +8706,6 @@ impl Default for settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::from( [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9032,24 +9014,6 @@ impl Default for settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::from( [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), ( "billing.email".to_string(), RequiredFieldInfo { @@ -9733,24 +9697,6 @@ impl Default for settings::RequiredFields { non_mandate: HashMap::new(), common: HashMap::from( [ - ( - "billing.address.first_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.first_name".to_string(), - display_name: "first_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), - ( - "billing.address.last_name".to_string(), - RequiredFieldInfo { - required_field: "payment_method_data.billing.address.last_name".to_string(), - display_name: "last_name".to_string(), - field_type: enums::FieldType::UserFullName, - value: None, - } - ), ( "billing.email".to_string(), RequiredFieldInfo { From a614c200498e6859ac5a936916bc80abeed73f12 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Wed, 5 Feb 2025 00:15:37 +0530 Subject: [PATCH 027/133] fix(connector): Fix Paybox 3DS failing issue (#7153) Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> --- .../src/connectors/paybox/transformers.rs | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs index effe6df28db..9d01036f660 100644 --- a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs @@ -148,7 +148,7 @@ pub struct ThreeDSPaymentsRequest { address1: Secret, zip_code: Secret, city: String, - country_code: api_models::enums::CountryAlpha2, + country_code: String, total_quantity: i32, } #[derive(Debug, Serialize, Eq, PartialEq)] @@ -422,7 +422,11 @@ impl TryFrom<&PayboxRouterData<&types::PaymentsAuthorizeRouterData>> for PayboxP address1: address.get_line1()?.clone(), zip_code: address.get_zip()?.clone(), city: address.get_city()?.clone(), - country_code: *address.get_country()?, + country_code: format!( + "{:03}", + common_enums::Country::from_alpha2(*address.get_country()?) + .to_numeric() + ), total_quantity: 1, })) } else { @@ -601,7 +605,7 @@ pub struct TransactionResponse { pub fn parse_url_encoded_to_struct( query_bytes: Bytes, ) -> CustomResult { - let (cow, _, _) = encoding_rs::ISO_8859_10.decode(&query_bytes); + let (cow, _, _) = encoding_rs::ISO_8859_15.decode(&query_bytes); serde_qs::from_str::(cow.as_ref()).change_context(errors::ConnectorError::ParsingFailed) } @@ -609,12 +613,13 @@ pub fn parse_paybox_response( query_bytes: Bytes, is_three_ds: bool, ) -> CustomResult { - let (cow, _, _) = encoding_rs::ISO_8859_10.decode(&query_bytes); - let response_str = cow.as_ref(); - - if response_str.starts_with("") && is_three_ds { + let (cow, _, _) = encoding_rs::ISO_8859_15.decode(&query_bytes); + let response_str = cow.as_ref().trim(); + if (response_str.starts_with("") || response_str.starts_with("")) + && is_three_ds + { let response = response_str.to_string(); - return Ok(if response.contains("Erreur") { + return Ok(if response.contains("Erreur 201") { PayboxResponse::Error(response) } else { PayboxResponse::ThreeDs(response.into()) From 5247a3c6512d39d5468188419cf22d362118ee7b Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 5 Feb 2025 00:26:52 +0000 Subject: [PATCH 028/133] chore(version): 2025.02.05.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d3abbda850..a6cc3a151af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.05.0 + +### Features + +- **router:** Add card_discovery in payment_attempt ([#7039](https://github.com/juspay/hyperswitch/pull/7039)) ([`b9aa3ab`](https://github.com/juspay/hyperswitch/commit/b9aa3ab445e7966dad3f7c09f27e644d5628f61f)) + +### Bug Fixes + +- **connector:** + - [novalnet] Remove first name, last name as required fields for Applepay, Googlepay, Paypal ([#7152](https://github.com/juspay/hyperswitch/pull/7152)) ([`f0b443e`](https://github.com/juspay/hyperswitch/commit/f0b443eda53bfb7b56679277e6077a8d55974763)) + - Fix Paybox 3DS failing issue ([#7153](https://github.com/juspay/hyperswitch/pull/7153)) ([`a614c20`](https://github.com/juspay/hyperswitch/commit/a614c200498e6859ac5a936916bc80abeed73f12)) +- **router:** + - [Cybersource] add flag to indicate final capture ([#7085](https://github.com/juspay/hyperswitch/pull/7085)) ([`55bb284`](https://github.com/juspay/hyperswitch/commit/55bb284ba063dc84e80b4f0d83c82ec7c30ad4c5)) + - Add dynamic fields support for `samsung_pay` ([#7090](https://github.com/juspay/hyperswitch/pull/7090)) ([`e2ddcc2`](https://github.com/juspay/hyperswitch/commit/e2ddcc26b84e4ddcd69005080e19d211b1604827)) +- Invalidate surcharge cache during update ([#6907](https://github.com/juspay/hyperswitch/pull/6907)) ([`8ac1b83`](https://github.com/juspay/hyperswitch/commit/8ac1b83985dbae33afc3b53d46b85a374ff3c1e9)) + +**Full Changelog:** [`2025.02.04.0...2025.02.05.0`](https://github.com/juspay/hyperswitch/compare/2025.02.04.0...2025.02.05.0) + +- - - + ## 2025.02.04.0 ### Features From 6fee3011ea84e08caef8459cd1f55856245e15b2 Mon Sep 17 00:00:00 2001 From: Arindam Sahoo <88739246+arindam-sahoo@users.noreply.github.com> Date: Wed, 5 Feb 2025 14:55:10 +0530 Subject: [PATCH 029/133] refactor(ci): Remove Adyen-specific deprecated PMTs Sofort test cases in Postman (#7099) Co-authored-by: Arindam Sahoo Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> --- .../Flow Testcases/Happy Cases/.meta.json | 19 ++-- .../.meta.json | 0 .../Payments - Confirm/.event.meta.json | 0 .../Payments - Confirm/event.test.js | 0 .../Payments - Confirm/request.json | 0 .../Payments - Confirm/response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve/response.json | 0 .../Payments - Confirm/event.test.js | 103 ------------------ .../Payments - Confirm/request.json | 83 -------------- .../Payments - Create/request.json | 88 --------------- .../.meta.json | 0 .../Payments - Create}/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create}/response.json | 0 .../.event.meta.json | 0 .../Payments - Retrieve-copy/event.test.js | 0 .../Payments - Retrieve-copy}/request.json | 0 .../Payments - Retrieve-copy}/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve}/request.json | 0 .../Payments - Retrieve/response.json | 0 .../.event.meta.json | 0 .../Recurring Payments - Create/event.test.js | 0 .../Recurring Payments - Create/request.json | 0 .../response.json | 0 .../Refunds - Create Copy}/.event.meta.json | 0 .../Refunds - Create Copy/event.test.js | 0 .../Refunds - Create Copy/request.json | 0 .../Refunds - Create Copy}/response.json | 0 .../Refunds - Retrieve Copy}/.event.meta.json | 0 .../Refunds - Retrieve Copy/event.test.js | 0 .../Refunds - Retrieve Copy/request.json | 0 .../Refunds - Retrieve Copy}/response.json | 0 .../.meta.json | 0 .../Payments - Confirm}/.event.meta.json | 0 .../Payments - Confirm/event.test.js | 0 .../Payments - Confirm/request.json | 0 .../Payments - Confirm}/response.json | 0 .../Payments - Create}/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create}/response.json | 0 .../Payments - Retrieve}/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve}/response.json | 0 .../.meta.json | 0 .../Payments - Confirm/.event.meta.json | 0 .../Payments - Confirm/event.test.js | 0 .../Payments - Confirm/request.json | 0 .../Payments - Confirm/response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve/response.json | 0 .../.meta.json | 0 .../Payments - Confirm/.event.meta.json | 0 .../Payments - Confirm/event.test.js | 0 .../Payments - Confirm/request.json | 0 .../Payments - Confirm/response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve/response.json | 0 .../.meta.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../request.json | 0 .../response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create/response.json | 0 .../Payments - Retrieve Copy/.event.meta.json | 0 .../Payments - Retrieve Copy/event.test.js | 0 .../Payments - Retrieve Copy/request.json | 0 .../Payments - Retrieve Copy}/response.json | 0 .../Refunds - Create/.event.meta.json | 0 .../Refunds - Create/event.test.js | 0 .../Refunds - Create/request.json | 0 .../Refunds - Create}/response.json | 0 .../Refunds - Retrieve/.event.meta.json | 0 .../Refunds - Retrieve/event.test.js | 0 .../Refunds - Retrieve/request.json | 0 .../Refunds - Retrieve}/response.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../Save card payments - Confirm/request.json | 0 .../response.json | 0 .../.event.meta.json | 0 .../Save card payments - Create/event.test.js | 0 .../Save card payments - Create/request.json | 0 .../response.json | 0 .../.meta.json | 7 -- .../Payments - Create/event.test.js | 71 ------------ .../Payments - Retrieve/.event.meta.json | 3 - .../Payments - Retrieve/event.test.js | 71 ------------ .../.meta.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../request.json | 0 .../response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.prerequest.js | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create}/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve}/response.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../Save card payments - Confirm/request.json | 0 .../response.json | 0 .../.event.meta.json | 0 .../Save card payments - Create/event.test.js | 0 .../Save card payments - Create/request.json | 0 .../response.json | 0 .../.meta.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../request.json | 0 .../response.json | 0 .../Payments - Create/.event.meta.json | 0 .../Payments - Create/event.prerequest.js | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create}/response.json | 0 .../Payments - Retrieve/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve}/response.json | 0 .../.event.meta.json | 0 .../event.test.js | 0 .../Save card payments - Confirm/request.json | 0 .../response.json | 0 .../.event.meta.json | 0 .../Save card payments - Create/event.test.js | 0 .../Save card payments - Create/request.json | 0 .../response.json | 0 .../.meta.json | 0 .../Payments - Create}/.event.meta.json | 0 .../Payments - Create/event.test.js | 0 .../Payments - Create/request.json | 0 .../Payments - Create}/response.json | 0 .../Payments - Retrieve}/.event.meta.json | 0 .../Payments - Retrieve/event.test.js | 0 .../Payments - Retrieve/request.json | 0 .../Payments - Retrieve}/response.json | 0 .../Save card payments - Create/response.json | 1 - .../Payments - Create/.event.meta.json | 3 - .../Payments - Create/response.json | 1 - .../Payments - Retrieve/.event.meta.json | 3 - .../Payments - Retrieve/request.json | 28 ----- .../Payments - Retrieve/response.json | 1 - .../adyen_uk.postman_collection.json | 2 +- 174 files changed, 10 insertions(+), 474 deletions(-) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario11-Bank Redirect-eps}/Payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario11-Bank Redirect-eps}/Payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Confirm/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario11-Bank Redirect-eps}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Create/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario11-Bank Redirect-sofort => Scenario11-Bank Redirect-eps}/Payments - Retrieve/response.json (100%) delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/event.test.js delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/request.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/request.json rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps/Payments - Confirm => Scenario12-Refund recurring payment/Payments - Create}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps/Payments - Confirm => Scenario12-Refund recurring payment/Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps/Payments - Create => Scenario12-Refund recurring payment/Payments - Retrieve-copy}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Payments - Retrieve-copy/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps/Payments - Retrieve => Scenario12-Refund recurring payment/Payments - Retrieve-copy}/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps/Payments - Create => Scenario12-Refund recurring payment/Payments - Retrieve-copy}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario12-Refund recurring payment}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Retrieve-copy => Scenario12-Refund recurring payment/Payments - Retrieve}/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario12-Refund recurring payment}/Payments - Retrieve/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Create => Scenario12-Refund recurring payment/Recurring Payments - Create}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Recurring Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Recurring Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Create => Scenario12-Refund recurring payment/Recurring Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Retrieve-copy => Scenario12-Refund recurring payment/Refunds - Create Copy}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Refunds - Create Copy/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Refunds - Create Copy/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Retrieve-copy => Scenario12-Refund recurring payment/Refunds - Create Copy}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Retrieve => Scenario12-Refund recurring payment/Refunds - Retrieve Copy}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Refunds - Retrieve Copy/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario12-Refund recurring payment}/Refunds - Retrieve Copy/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Payments - Retrieve => Scenario12-Refund recurring payment/Refunds - Retrieve Copy}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario13-Bank debit-ach}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Recurring Payments - Create => Scenario13-Bank debit-ach/Payments - Confirm}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario13-Bank debit-ach}/Payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario13-Bank debit-ach}/Payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Recurring Payments - Create => Scenario13-Bank debit-ach/Payments - Confirm}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Refunds - Create Copy => Scenario13-Bank debit-ach/Payments - Create}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario13-Bank debit-ach}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario13-Bank debit-ach}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Refunds - Create Copy => Scenario13-Bank debit-ach/Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Refunds - Retrieve Copy => Scenario13-Bank debit-ach/Payments - Retrieve}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario13-Bank debit-ach}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment => Scenario13-Bank debit-ach}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario13-Refund recurring payment/Refunds - Retrieve Copy => Scenario13-Bank debit-ach/Payments - Retrieve}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario14-Bank debit-Bacs}/Payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario14-Bank debit-Bacs}/Payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Confirm/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario14-Bank debit-Bacs}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Create/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario14-Bank debit-Bacs}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario14-Bank debit-ach => Scenario14-Bank debit-Bacs}/Payments - Retrieve/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly => Scenario15-Bank Redirect-Trustly}/Payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly => Scenario15-Bank Redirect-Trustly}/Payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Confirm/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly => Scenario15-Bank Redirect-Trustly}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Create/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario12-Bank Redirect-eps => Scenario15-Bank Redirect-Trustly}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario15-Bank debit-Bacs => Scenario15-Bank Redirect-Trustly}/Payments - Retrieve/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/List payment methods for a Customer/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/List payment methods for a Customer/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/List payment methods for a Customer/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly/Payments - Confirm => Scenario16-Add card flow/List payment methods for a Customer}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly => Scenario16-Add card flow}/Payments - Create/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Retrieve Copy/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Retrieve Copy/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Payments - Retrieve Copy/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly/Payments - Retrieve => Scenario16-Add card flow/Payments - Retrieve Copy}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/List payment methods for a Customer => Scenario16-Add card flow/Refunds - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Refunds - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Payments - Create => Scenario16-Add card flow/Refunds - Retrieve}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Payments - Retrieve Copy => Scenario16-Add card flow/Save card payments - Confirm}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow => Scenario16-Add card flow}/Save card payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Refunds - Create => Scenario16-Add card flow/Save card payments - Create}/response.json (100%) delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/.meta.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/event.test.js delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/event.test.js rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/List payment methods for a Customer/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/List payment methods for a Customer/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/List payment methods for a Customer/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Refunds - Retrieve => Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Create/event.prerequest.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Save card payments - Confirm => Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario17-Add card flow/Save card payments - Create => Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer => Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment => Scenario17-Pass Invalid CVV for save card flow and verify failed payment}/Save card payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create => Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/List payment methods for a Customer/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/List payment methods for a Customer/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/List payment methods for a Customer/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Create/event.prerequest.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Retrieve/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Confirm/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Confirm/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Confirm/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Create/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy}/Save card payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create => Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario20-Create Gift Card payment => Scenario19-Create Gift Card payment}/.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly/Payments - Confirm => Scenario19-Create Gift Card payment/Payments - Create}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario20-Create Gift Card payment => Scenario19-Create Gift Card payment}/Payments - Create/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario20-Create Gift Card payment => Scenario19-Create Gift Card payment}/Payments - Create/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve => Scenario19-Create Gift Card payment/Payments - Create}/response.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly/Payments - Create => Scenario19-Create Gift Card payment/Payments - Retrieve}/.event.meta.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario20-Create Gift Card payment => Scenario19-Create Gift Card payment}/Payments - Retrieve/event.test.js (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario16-Bank Redirect-Trustly => Scenario19-Create Gift Card payment}/Payments - Retrieve/request.json (100%) rename postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/{Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm => Scenario19-Create Gift Card payment/Payments - Retrieve}/response.json (100%) delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/.event.meta.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/response.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/.event.meta.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/request.json delete mode 100644 postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/.meta.json index 8bfdb580671..6980f1fe85e 100644 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/.meta.json +++ b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/.meta.json @@ -10,15 +10,14 @@ "Scenario8-Refund full payment", "Scenario9-Create a mandate and recurring payment", "Scenario10-Partial refund", - "Scenario11-Bank Redirect-sofort", - "Scenario12-Bank Redirect-eps", - "Scenario13-Refund recurring payment", - "Scenario14-Bank debit-ach", - "Scenario15-Bank debit-Bacs", - "Scenario16-Bank Redirect-Trustly", - "Scenario17-Add card flow", - "Scenario18-Pass Invalid CVV for save card flow and verify failed payment", - "Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy", - "Scenario20-Create Gift Card payment" + "Scenario11-Bank Redirect-eps", + "Scenario12-Refund recurring payment", + "Scenario13-Bank debit-ach", + "Scenario14-Bank debit-Bacs", + "Scenario15-Bank Redirect-Trustly", + "Scenario16-Add card flow", + "Scenario17-Pass Invalid CVV for save card flow and verify failed payment", + "Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy", + "Scenario19-Create Gift Card payment" ] } \ No newline at end of file diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-eps/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/event.test.js deleted file mode 100644 index d4b9fbead7e..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/event.test.js +++ /dev/null @@ -1,103 +0,0 @@ -// Validate status 2xx -pm.test("[POST]::/payments/:id/confirm - Status code is 2xx", function () { - pm.response.to.be.success; -}); - -// Validate if response header has matching content-type -pm.test( - "[POST]::/payments/:id/confirm - 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/:id/confirm - 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 "requires_customer_action" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'", - function () { - pm.expect(jsonData.status).to.eql("requires_customer_action"); - }, - ); -} - -// Response body should have "next_action.redirect_to_url" -pm.test( - "[POST]::/payments - Content check if 'next_action.redirect_to_url' exists", - function () { - pm.expect(typeof jsonData.next_action.redirect_to_url !== "undefined").to.be - .true; - }, -); - -// Response body should have value "sofort" for "payment_method_type" -if (jsonData?.payment_method_type) { - pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'sofort'", - function () { - pm.expect(jsonData.payment_method_type).to.eql("sofort"); - }, - ); -} - -// Response body should have value "stripe" for "connector" -if (jsonData?.connector) { - pm.test( - "[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'adyen'", - function () { - pm.expect(jsonData.connector).to.eql("adyen"); - }, - ); -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/request.json deleted file mode 100644 index 001749a500f..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Confirm/request.json +++ /dev/null @@ -1,83 +0,0 @@ -{ - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw_json_formatted": { - "client_secret": "{{client_secret}}", - "payment_method": "bank_redirect", - "payment_method_type": "sofort", - "payment_method_data": { - "bank_redirect": { - "sofort": { - "billing_details": { - "billing_name": "John Doe" - }, - "bank_name": "ing", - "preferred_language": "en", - "country": "DE" - } - } - }, - "browser_info": { - "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "language": "nl-NL", - "color_depth": 24, - "screen_height": 723, - "screen_width": 1536, - "time_zone": 0, - "java_enabled": true, - "java_script_enabled": true, - "ip_address": "127.0.0.1" - } - } - }, - "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", - "host": ["{{baseUrl}}"], - "path": ["payments", ":id", "confirm"], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/request.json deleted file mode 100644 index 0b0c56d2660..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario11-Bank Redirect-sofort/Payments - Create/request.json +++ /dev/null @@ -1,88 +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": 1000, - "currency": "EUR", - "confirm": false, - "capture_method": "automatic", - "capture_on": "2022-09-10T10:11:12Z", - "amount_to_capture": 1000, - "customer_id": "StripeCustomer", - "email": "abcdef123@gmail.com", - "name": "John Doe", - "phone": "999999999", - "phone_country_code": "+65", - "description": "Its my first payment request", - "authentication_type": "three_ds", - "return_url": "https://duck.com", - "billing": { - "address": { - "first_name": "John", - "last_name": "Doe", - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "DE" - } - }, - "browser_info": { - "user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36", - "accept_header": "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", - "language": "nl-NL", - "color_depth": 24, - "screen_height": 723, - "screen_width": 1536, - "time_zone": 0, - "java_enabled": true, - "java_script_enabled": true, - "ip_address": "127.0.0.1" - }, - "shipping": { - "address": { - "line1": "1467", - "line2": "Harrison Street", - "line3": "Harrison Street", - "city": "San Fransico", - "state": "California", - "zip": "94122", - "country": "US", - "first_name": "John", - "last_name": "Doe" - } - }, - "statement_descriptor_name": "joseph", - "statement_descriptor_suffix": "JS", - "metadata": { - "udf1": "value1", - "new_customer": "true", - "login_date": "2019-09-10T10:11:12Z" - } - } - }, - "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/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve-copy/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Recurring Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve-copy/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Create Copy/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Refund recurring payment/Refunds - Retrieve Copy/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Recurring Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Create Copy/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Refund recurring payment/Refunds - Retrieve Copy/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario13-Bank debit-ach/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-ach/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario14-Bank debit-Bacs/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario12-Bank Redirect-eps/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank debit-Bacs/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario15-Bank Redirect-Trustly/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/List payment methods for a Customer/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Payments - Retrieve Copy/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/List payment methods for a Customer/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Refunds - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Payments - Retrieve Copy/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Add card flow/Save card payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/.meta.json deleted file mode 100644 index 57d3f8e2bc7..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/.meta.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "childrenOrder": [ - "Payments - Create", - "Payments - Confirm", - "Payments - Retrieve" - ] -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/event.test.js deleted file mode 100644 index 0444324000a..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/event.test.js +++ /dev/null @@ -1,71 +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 "requires_payment_method" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'", - function () { - pm.expect(jsonData.status).to.eql("requires_payment_method"); - }, - ); -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/event.test.js deleted file mode 100644 index 9053ddab13b..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/event.test.js +++ /dev/null @@ -1,71 +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 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 "requires_customer_action" for "status" -if (jsonData?.status) { - pm.test( - "[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'", - function () { - pm.expect(jsonData.status).to.eql("requires_customer_action"); - }, - ); -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Refunds - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.prerequest.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.prerequest.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Add card flow/Save card payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/List payment methods for a Customer/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario17-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.prerequest.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.prerequest.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.prerequest.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.prerequest.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Pass Invalid CVV for save card flow and verify failed payment/Save card payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/List payment methods for a Customer/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Create/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Confirm/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Payments - Retrieve/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Create/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/.event.meta.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Create/.event.meta.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/.event.meta.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/event.test.js b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/event.test.js similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/event.test.js rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/event.test.js diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/request.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario16-Bank Redirect-Trustly/Payments - Retrieve/request.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/request.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/response.json similarity index 100% rename from postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Confirm/response.json rename to postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Create Gift Card payment/Payments - Retrieve/response.json diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json deleted file mode 100644 index fe51488c706..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy/Save card payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/response.json deleted file mode 100644 index fe51488c706..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Create/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/.event.meta.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/.event.meta.json deleted file mode 100644 index 0731450e6b2..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/.event.meta.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "eventOrder": ["event.test.js"] -} diff --git a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/request.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/request.json deleted file mode 100644 index 6cd4b7d96c5..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/request.json +++ /dev/null @@ -1,28 +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/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/response.json b/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/response.json deleted file mode 100644 index fe51488c706..00000000000 --- a/postman/collection-dir/adyen_uk/Flow Testcases/Happy Cases/Scenario20-Create Gift Card payment/Payments - Retrieve/response.json +++ /dev/null @@ -1 +0,0 @@ -[] diff --git a/postman/collection-json/adyen_uk.postman_collection.json b/postman/collection-json/adyen_uk.postman_collection.json index 512f84c6a4b..ebf353bb88d 100644 --- a/postman/collection-json/adyen_uk.postman_collection.json +++ b/postman/collection-json/adyen_uk.postman_collection.json @@ -14375,4 +14375,4 @@ "type": "string" } ] -} +} \ No newline at end of file From e0ec27d936fc62a6feb2f8f643a218f3ad7483b5 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Wed, 5 Feb 2025 15:24:57 +0530 Subject: [PATCH 030/133] feat(core): google pay decrypt flow (#6991) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> --- .deepsource.toml | 2 +- Cargo.lock | 9 + Cargo.toml | 2 +- INSTALL_dependencies.sh | 2 +- api-reference-v2/openapi_spec.json | 5 + api-reference/openapi_spec.json | 5 + config/config.example.toml | 3 + config/deployments/env_specific.toml | 3 + config/development.toml | 3 + crates/api_models/src/admin.rs | 4 + crates/api_models/src/payments.rs | 51 ++ crates/cards/src/lib.rs | 4 +- crates/common_enums/src/enums.rs | 10 + crates/common_utils/Cargo.toml | 1 + crates/common_utils/src/custom_serde.rs | 16 +- crates/common_utils/src/lib.rs | 10 + .../connectors/bankofamerica/transformers.rs | 10 + .../connectors/cybersource/transformers.rs | 162 +++++- .../src/connectors/fiuu/transformers.rs | 3 + .../src/connectors/gocardless/transformers.rs | 4 +- .../src/connectors/mollie/transformers.rs | 3 + .../src/connectors/square/transformers.rs | 3 + .../src/connectors/stax/transformers.rs | 6 + .../src/connectors/wellsfargo/transformers.rs | 6 + .../src/router_data.rs | 22 + crates/masking/src/secret.rs | 7 + crates/router/Cargo.toml | 2 + .../src/configs/secrets_transformers.rs | 33 ++ crates/router/src/configs/settings.rs | 11 + crates/router/src/configs/validations.rs | 15 + .../src/connector/braintree/transformers.rs | 6 + .../src/connector/checkout/transformers.rs | 6 + .../src/connector/payme/transformers.rs | 3 + .../src/connector/stripe/transformers.rs | 3 + crates/router/src/core/errors.rs | 54 ++ crates/router/src/core/payments.rs | 144 ++++- crates/router/src/core/payments/helpers.rs | 544 ++++++++++++++++++ .../router/src/core/payments/tokenization.rs | 5 + crates/router/src/types.rs | 3 +- 39 files changed, 1158 insertions(+), 27 deletions(-) diff --git a/.deepsource.toml b/.deepsource.toml index 3cac181614e..8d0b3c501af 100644 --- a/.deepsource.toml +++ b/.deepsource.toml @@ -13,4 +13,4 @@ name = "rust" enabled = true [analyzers.meta] -msrv = "1.78.0" +msrv = "1.80.0" diff --git a/Cargo.lock b/Cargo.lock index 7583fb2eeee..7e2ab6eccd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1528,6 +1528,12 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "base64-serde" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c6d128af408d8ebd08331f0331cf2cf20d19e6c44a7aec58791641ecc8c0b5" + [[package]] name = "base64-simd" version = "0.8.0" @@ -2044,6 +2050,7 @@ version = "0.1.0" dependencies = [ "async-trait", "base64 0.22.1", + "base64-serde", "blake3", "bytes 1.7.1", "common_enums", @@ -6581,6 +6588,7 @@ dependencies = [ "external_services", "futures 0.3.30", "hex", + "hkdf", "http 0.2.12", "hyper 0.14.30", "hyperswitch_connectors", @@ -6629,6 +6637,7 @@ dependencies = [ "serde_with", "serial_test", "sha1", + "sha2", "storage_impl", "strum 0.26.3", "tera", diff --git a/Cargo.toml b/Cargo.toml index dc60c64f667..94e717c5b79 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -2,7 +2,7 @@ resolver = "2" members = ["crates/*"] package.edition = "2021" -package.rust-version = "1.78.0" +package.rust-version = "1.80.0" package.license = "Apache-2.0" [workspace.dependencies] diff --git a/INSTALL_dependencies.sh b/INSTALL_dependencies.sh index e1d666b97a3..bc7d502b357 100755 --- a/INSTALL_dependencies.sh +++ b/INSTALL_dependencies.sh @@ -9,7 +9,7 @@ if [[ "${TRACE-0}" == "1" ]]; then set -o xtrace fi -RUST_MSRV="1.78.0" +RUST_MSRV="1.80.0" _DB_NAME="hyperswitch_db" _DB_USER="db_user" _DB_PASS="db_password" diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index b315800b08c..447010178de 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6875,6 +6875,11 @@ "type": "object", "description": "This field contains the Paze certificates and credentials", "nullable": true + }, + "google_pay": { + "type": "object", + "description": "This field contains the Google Pay certificates and credentials", + "nullable": true } }, "additionalProperties": false diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 0aa2c898fc6..fa631eee788 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9523,6 +9523,11 @@ "type": "object", "description": "This field contains the Paze certificates and credentials", "nullable": true + }, + "google_pay": { + "type": "object", + "description": "This field contains the Google Pay certificates and credentials", + "nullable": true } }, "additionalProperties": false diff --git a/config/config.example.toml b/config/config.example.toml index b76905eb534..c113d7abd57 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -607,6 +607,9 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key paze_private_key = "PAZE_PRIVATE_KEY" # Base 64 Encoded Private Key File cakey.pem generated for Paze -> Command to create private key: openssl req -newkey rsa:2048 -x509 -keyout cakey.pem -out cacert.pem -days 365 paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" # PEM Passphrase used for generating Private Key File cakey.pem +[google_pay_decrypt_keys] +google_pay_root_signing_keys = "GOOGLE_PAY_ROOT_SIGNING_KEYS" # Base 64 Encoded Root Signing Keys provided by Google Pay (https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography) + [applepay_merchant_configs] # Run below command to get common merchant identifier for applepay in shell # diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 8c846ff422e..4e5fa3dba44 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -34,6 +34,9 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" # Private key paze_private_key = "PAZE_PRIVATE_KEY" # Base 64 Encoded Private Key File cakey.pem generated for Paze -> Command to create private key: openssl req -newkey rsa:2048 -x509 -keyout cakey.pem -out cacert.pem -days 365 paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" # PEM Passphrase used for generating Private Key File cakey.pem +[google_pay_decrypt_keys] +google_pay_root_signing_keys = "GOOGLE_PAY_ROOT_SIGNING_KEYS" # Base 64 Encoded Root Signing Keys provided by Google Pay (https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography) + [applepay_merchant_configs] common_merchant_identifier = "APPLE_PAY_COMMON_MERCHANT_IDENTIFIER" # Refer to config.example.toml to learn how you can generate this value merchant_cert = "APPLE_PAY_MERCHANT_CERTIFICATE" # Merchant Certificate provided by Apple Pay (https://developer.apple.com/) Certificates, Identifiers & Profiles > Apple Pay Merchant Identity Certificate diff --git a/config/development.toml b/config/development.toml index 8209d49565f..2b81123d873 100644 --- a/config/development.toml +++ b/config/development.toml @@ -671,6 +671,9 @@ apple_pay_merchant_cert_key = "APPLE_PAY_MERCHNAT_CERTIFICATE_KEY" paze_private_key = "PAZE_PRIVATE_KEY" paze_private_key_passphrase = "PAZE_PRIVATE_KEY_PASSPHRASE" +[google_pay_decrypt_keys] +google_pay_root_signing_keys = "GOOGLE_PAY_ROOT_SIGNING_KEYS" # Base 64 Encoded Root Signing Keys provided by Google Pay (https://developers.google.com/pay/api/web/guides/resources/payment-data-cryptography) + [generic_link] [generic_link.payment_method_collect] sdk_url = "http://localhost:9050/HyperLoader.js" diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 321ee5305f1..e47f7826d59 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -1468,6 +1468,10 @@ pub struct ConnectorWalletDetails { #[serde(skip_serializing_if = "Option::is_none")] #[schema(value_type = Option)] pub paze: Option, + /// This field contains the Google Pay certificates and credentials + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub google_pay: Option, } /// Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc." diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 2c8723857a8..3a6fe000afe 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5984,6 +5984,57 @@ pub struct SessionTokenForSimplifiedApplePay { pub merchant_business_country: Option, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayWalletDetails { + pub google_pay: GooglePayDetails, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayDetails { + pub provider_details: GooglePayProviderDetails, +} + +// Google Pay Provider Details can of two types: GooglePayMerchantDetails or GooglePayHyperSwitchDetails +// GooglePayHyperSwitchDetails is not implemented yet +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(untagged)] +pub enum GooglePayProviderDetails { + GooglePayMerchantDetails(GooglePayMerchantDetails), +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayMerchantDetails { + pub merchant_info: GooglePayMerchantInfo, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayMerchantInfo { + pub merchant_name: String, + pub tokenization_specification: GooglePayTokenizationSpecification, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayTokenizationSpecification { + #[serde(rename = "type")] + pub tokenization_type: GooglePayTokenizationType, + pub parameters: GooglePayTokenizationParameters, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GooglePayTokenizationType { + PaymentGateway, + Direct, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayTokenizationParameters { + pub gateway: String, + pub public_key: Secret, + pub private_key: Secret, + pub recipient_id: Option>, +} + #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] #[serde(tag = "wallet_name")] #[serde(rename_all = "snake_case")] diff --git a/crates/cards/src/lib.rs b/crates/cards/src/lib.rs index 91cb93301a9..0c0fb9d47a1 100644 --- a/crates/cards/src/lib.rs +++ b/crates/cards/src/lib.rs @@ -35,7 +35,7 @@ impl<'de> Deserialize<'de> for CardSecurityCode { } } -#[derive(Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct CardExpirationMonth(StrongSecret); impl CardExpirationMonth { @@ -67,7 +67,7 @@ impl<'de> Deserialize<'de> for CardExpirationMonth { } } -#[derive(Serialize)] +#[derive(Clone, Debug, Serialize)] pub struct CardExpirationYear(StrongSecret); impl CardExpirationYear { diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index c30c9ec9f14..2f5249ded57 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3675,3 +3675,13 @@ pub enum FeatureStatus { NotSupported, Supported, } + +#[derive(Clone, Copy, Debug, Deserialize, Serialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GooglePayAuthMethod { + /// Contain pan data only + PanOnly, + /// Contain cryptogram data along with pan data + #[serde(rename = "CRYPTOGRAM_3DS")] + Cryptogram, +} diff --git a/crates/common_utils/Cargo.toml b/crates/common_utils/Cargo.toml index dcdb4c760b1..3d31c7a6a98 100644 --- a/crates/common_utils/Cargo.toml +++ b/crates/common_utils/Cargo.toml @@ -26,6 +26,7 @@ payment_methods_v2 = [] [dependencies] async-trait = { version = "0.1.79", optional = true } base64 = "0.22.0" +base64-serde = "0.8.0" blake3 = { version = "1.5.1", features = ["serde"] } bytes = "1.6.0" diesel = "2.2.3" diff --git a/crates/common_utils/src/custom_serde.rs b/crates/common_utils/src/custom_serde.rs index 63ef30011f7..06087f082e0 100644 --- a/crates/common_utils/src/custom_serde.rs +++ b/crates/common_utils/src/custom_serde.rs @@ -203,8 +203,20 @@ pub mod timestamp { /// pub mod json_string { - use serde::de::{self, Deserialize, DeserializeOwned, Deserializer}; - use serde_json; + use serde::{ + de::{self, Deserialize, DeserializeOwned, Deserializer}, + ser::{self, Serialize, Serializer}, + }; + + /// Serialize a type to json_string format + pub fn serialize(value: &T, serializer: S) -> Result + where + T: Serialize, + S: Serializer, + { + let j = serde_json::to_string(value).map_err(ser::Error::custom)?; + j.serialize(serializer) + } /// Deserialize a string which is in json format pub fn deserialize<'de, T, D>(deserializer: D) -> Result diff --git a/crates/common_utils/src/lib.rs b/crates/common_utils/src/lib.rs index 463ec3ee1b6..fd0c935035f 100644 --- a/crates/common_utils/src/lib.rs +++ b/crates/common_utils/src/lib.rs @@ -35,6 +35,8 @@ pub mod hashing; #[cfg(feature = "metrics")] pub mod metrics; +pub use base64_serializer::Base64Serializer; + /// Date-time utilities. pub mod date_time { #[cfg(feature = "async_ext")] @@ -296,6 +298,14 @@ pub trait DbConnectionParams { } } +// Can't add doc comments for macro invocations, neither does the macro allow it. +#[allow(missing_docs)] +mod base64_serializer { + use base64_serde::base64_serde_type; + + base64_serde_type!(pub Base64Serializer, crate::consts::BASE64_ENGINE); +} + #[cfg(test)] mod nanoid_tests { #![allow(clippy::unwrap_used)] diff --git a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index 3602f771928..b33555a7c55 100644 --- a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -962,6 +962,12 @@ impl TryFrom<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> PaymentMethodToken::PazeDecrypt(_) => Err( unimplemented_payment_method!("Paze", "Bank Of America"), )?, + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!( + "Google Pay", + "Bank Of America" + ))? + } }, None => { let email = item.router_data.request.get_email()?; @@ -2279,6 +2285,10 @@ impl TryFrom<(&SetupMandateRouterData, ApplePayWalletData)> for BankOfAmericaPay PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Bank Of America"))? } + PaymentMethodToken::GooglePayDecrypt(_) => Err(unimplemented_payment_method!( + "Google Pay", + "Bank Of America" + ))?, }, None => PaymentInformation::from(&apple_pay_data), }; diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 5fc9cad4ffe..b14ffc9c79e 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -24,7 +24,8 @@ use hyperswitch_domain_models::{ }, router_data::{ AdditionalPaymentMethodConnectorResponse, ApplePayPredecryptData, ConnectorAuthType, - ConnectorResponseData, ErrorResponse, PaymentMethodToken, RouterData, + ConnectorResponseData, ErrorResponse, GooglePayDecryptedData, PaymentMethodToken, + RouterData, }, router_flow_types::{ payments::Authorize, @@ -196,9 +197,9 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { ApplePayPaymentInformation { tokenized_card: TokenizedCard { number: decrypt_data.application_primary_account_number, - cryptogram: decrypt_data - .payment_data - .online_payment_cryptogram, + cryptogram: Some( + decrypt_data.payment_data.online_payment_cryptogram, + ), transaction_type: TransactionType::ApplePay, expiration_year, expiration_month, @@ -216,6 +217,9 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Cybersource"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Cybersource"))? + } }, None => ( PaymentInformation::ApplePayToken(Box::new( @@ -233,15 +237,17 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { ), }, WalletData::GooglePay(google_pay_data) => ( - PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { - fluid_data: FluidData { - value: Secret::from( - consts::BASE64_ENGINE - .encode(google_pay_data.tokenization_data.token), - ), - descriptor: None, + PaymentInformation::GooglePayToken(Box::new( + GooglePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from( + consts::BASE64_ENGINE + .encode(google_pay_data.tokenization_data.token), + ), + descriptor: None, + }, }, - })), + )), Some(PaymentSolution::GooglePay), ), WalletData::AliPayQr(_) @@ -448,7 +454,7 @@ pub struct TokenizedCard { number: Secret, expiration_month: Secret, expiration_year: Secret, - cryptogram: Secret, + cryptogram: Option>, transaction_type: TransactionType, } @@ -491,10 +497,16 @@ pub const FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY: &str = "FID=COMMON.SAMSUNG.INAP #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] -pub struct GooglePayPaymentInformation { +pub struct GooglePayTokenPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentInformation { + tokenized_card: TokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct SamsungPayTokenizedCard { @@ -520,6 +532,7 @@ pub struct SamsungPayFluidDataValue { #[serde(untagged)] pub enum PaymentInformation { Cards(Box), + GooglePayToken(Box), GooglePay(Box), ApplePay(Box), ApplePayToken(Box), @@ -588,6 +601,8 @@ pub enum TransactionType { ApplePay, #[serde(rename = "1")] SamsungPay, + #[serde(rename = "1")] + GooglePay, } impl From for String { @@ -1645,7 +1660,7 @@ impl PaymentInformation::ApplePay(Box::new(ApplePayPaymentInformation { tokenized_card: TokenizedCard { number: apple_pay_data.application_primary_account_number, - cryptogram: apple_pay_data.payment_data.online_payment_cryptogram, + cryptogram: Some(apple_pay_data.payment_data.online_payment_cryptogram), transaction_type: TransactionType::ApplePay, expiration_year, expiration_month, @@ -1707,7 +1722,7 @@ impl let order_information = OrderInformationWithBill::from((item, Some(bill_to))); let payment_information = - PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { + PaymentInformation::GooglePayToken(Box::new(GooglePayTokenPaymentInformation { fluid_data: FluidData { value: Secret::from( consts::BASE64_ENGINE.encode(google_pay_data.tokenization_data.token), @@ -1736,6 +1751,92 @@ impl } } +impl + TryFrom<( + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + Box, + GooglePayWalletData, + )> for CybersourcePaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + (item, google_pay_decrypted_data, google_pay_data): ( + &CybersourceRouterData<&PaymentsAuthorizeRouterData>, + Box, + GooglePayWalletData, + ), + ) -> Result { + let email = item + .router_data + .get_billing_email() + .or(item.router_data.request.get_email())?; + let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; + let order_information = OrderInformationWithBill::from((item, Some(bill_to))); + + let payment_information = + PaymentInformation::GooglePay(Box::new(GooglePayPaymentInformation { + tokenized_card: TokenizedCard { + number: Secret::new( + google_pay_decrypted_data + .payment_method_details + .pan + .get_card_no(), + ), + cryptogram: google_pay_decrypted_data.payment_method_details.cryptogram, + transaction_type: TransactionType::GooglePay, + expiration_year: Secret::new( + google_pay_decrypted_data + .payment_method_details + .expiration_year + .four_digits(), + ), + expiration_month: Secret::new( + google_pay_decrypted_data + .payment_method_details + .expiration_month + .two_digits(), + ), + }, + })); + let processing_information = ProcessingInformation::try_from(( + item, + Some(PaymentSolution::GooglePay), + Some(google_pay_data.info.card_network.clone()), + ))?; + let client_reference_information = ClientReferenceInformation::from(item); + let merchant_defined_information = item + .router_data + .request + .metadata + .clone() + .map(convert_metadata_to_merchant_defined_info); + + let ucaf_collection_indicator = + match google_pay_data.info.card_network.to_lowercase().as_str() { + "mastercard" => Some("2".to_string()), + _ => None, + }; + + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + consumer_authentication_information: Some(CybersourceConsumerAuthInformation { + ucaf_collection_indicator, + cavv: None, + ucaf_authentication_data: None, + xid: None, + directory_server_transaction_id: None, + specification_version: None, + pa_specification_version: None, + veres_enrolled: None, + }), + merchant_defined_information, + }) + } +} + impl TryFrom<( &CybersourceRouterData<&PaymentsAuthorizeRouterData>, @@ -1850,6 +1951,9 @@ impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for Cybersour PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Cybersource"))? } + PaymentMethodToken::GooglePayDecrypt(_) => Err( + unimplemented_payment_method!("Google Pay", "Cybersource"), + )?, }, None => { let email = item @@ -1917,7 +2021,31 @@ impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for Cybersour } } WalletData::GooglePay(google_pay_data) => { - Self::try_from((item, google_pay_data)) + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + PaymentMethodToken::GooglePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data, google_pay_data)) + } + PaymentMethodToken::Token(_) => { + Err(unimplemented_payment_method!( + "Apple Pay", + "Manual", + "Cybersource" + ))? + } + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Cybersource"))? + } + PaymentMethodToken::ApplePayDecrypt(_) => { + Err(unimplemented_payment_method!( + "Apple Pay", + "Simplified", + "Cybersource" + ))? + } + }, + None => Self::try_from((item, google_pay_data)), + } } WalletData::SamsungPay(samsung_pay_data) => { Self::try_from((item, samsung_pay_data)) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 51fc23eee41..115079c7d00 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -500,6 +500,9 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Fiuu"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Fiuu"))? + } } } WalletData::AliPayQr(_) diff --git a/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs b/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs index d64c2ed8efd..f09d30efc9d 100644 --- a/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/gocardless/transformers.rs @@ -437,7 +437,9 @@ impl TryFrom<&types::SetupMandateRouterData> for GocardlessMandateRequest { let payment_method_token = item.get_payment_method_token()?; let customer_bank_account = match payment_method_token { PaymentMethodToken::Token(token) => Ok(token), - PaymentMethodToken::ApplePayDecrypt(_) | PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::ApplePayDecrypt(_) + | PaymentMethodToken::PazeDecrypt(_) + | PaymentMethodToken::GooglePayDecrypt(_) => { Err(errors::ConnectorError::NotImplemented( "Setup Mandate flow for selected payment method through Gocardless".to_string(), )) diff --git a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs index 5a641327c96..fa2cb46c7b7 100644 --- a/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mollie/transformers.rs @@ -175,6 +175,9 @@ impl TryFrom<&MollieRouterData<&types::PaymentsAuthorizeRouterData>> for MollieP PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Mollie"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Mollie"))? + } }), }, ))) diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index 5abc93b3f88..ff3999aee64 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -272,6 +272,9 @@ impl TryFrom<&types::PaymentsAuthorizeRouterData> for SquarePaymentsRequest { PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Square"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Square"))? + } }, amount_money: SquarePaymentsAmountData { amount: item.request.amount, diff --git a/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs b/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs index a675e3040e3..327785d2fc8 100644 --- a/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/stax/transformers.rs @@ -85,6 +85,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Stax"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Stax"))? + } }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) @@ -105,6 +108,9 @@ impl TryFrom<&StaxRouterData<&types::PaymentsAuthorizeRouterData>> for StaxPayme PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Stax"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Stax"))? + } }, idempotency_id: Some(item.router_data.connector_request_reference_id.clone()), }) diff --git a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index 6ce9a4d7aa9..1a4d3ce8f9c 100644 --- a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -153,6 +153,9 @@ impl TryFrom<&SetupMandateRouterData> for WellsfargoZeroMandateRequest { PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? } + PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Wellsfargo"))? + } }, None => ( PaymentInformation::ApplePayToken(Box::new( @@ -1173,6 +1176,9 @@ impl TryFrom<&WellsfargoRouterData<&PaymentsAuthorizeRouterData>> for Wellsfargo PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Wellsfargo"))? } + PaymentMethodToken::GooglePayDecrypt(_) => Err( + unimplemented_payment_method!("Google Pay", "Wellsfargo"), + )?, }, None => { let email = item.router_data.request.get_email()?; diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 8e4c79a965e..322b4aa4525 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -226,6 +226,7 @@ pub struct AccessToken { pub enum PaymentMethodToken { Token(Secret), ApplePayDecrypt(Box), + GooglePayDecrypt(Box), PazeDecrypt(Box), } @@ -248,6 +249,27 @@ pub struct ApplePayCryptogramData { pub eci_indicator: Option, } +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayDecryptedData { + pub message_expiration: String, + pub message_id: String, + #[serde(rename = "paymentMethod")] + pub payment_method_type: String, + pub payment_method_details: GooglePayPaymentMethodDetails, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayPaymentMethodDetails { + pub auth_method: common_enums::enums::GooglePayAuthMethod, + pub expiration_month: cards::CardExpirationMonth, + pub expiration_year: cards::CardExpirationYear, + pub pan: cards::CardNumber, + pub cryptogram: Option>, + pub eci_indicator: Option, +} + #[derive(Debug, Clone, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct PazeDecryptedData { diff --git a/crates/masking/src/secret.rs b/crates/masking/src/secret.rs index 0bd28c3af92..7f7c18094a0 100644 --- a/crates/masking/src/secret.rs +++ b/crates/masking/src/secret.rs @@ -156,3 +156,10 @@ where SecretValue::default().into() } } + +// Required by base64-serde to serialize Secret of Vec which contains the base64 decoded value +impl AsRef<[u8]> for Secret> { + fn as_ref(&self) -> &[u8] { + self.peek().as_slice() + } +} diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index db65482628f..7db0273d2e1 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -68,6 +68,7 @@ encoding_rs = "0.8.33" error-stack = "0.4.1" futures = "0.3.30" hex = "0.4.3" +hkdf = "0.12.4" http = "0.2.12" hyper = "0.14.28" infer = "0.15.0" @@ -104,6 +105,7 @@ serde_repr = "0.1.19" serde_urlencoded = "0.7.1" serde_with = "3.7.0" sha1 = { version = "0.10.6" } +sha2 = "0.10.8" strum = { version = "0.26", features = ["derive"] } tera = "1.19.1" thiserror = "1.0.58" diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index 7b74aca7647..f51a785eaa8 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -199,6 +199,24 @@ impl SecretsHandler for settings::PazeDecryptConfig { } } +#[async_trait::async_trait] +impl SecretsHandler for settings::GooglePayDecryptConfig { + async fn convert_to_raw_secret( + value: SecretStateContainer, + secret_management_client: &dyn SecretManagementInterface, + ) -> CustomResult, SecretsManagementError> { + let google_pay_decrypt_keys = value.get_inner(); + + let google_pay_root_signing_keys = secret_management_client + .get_secret(google_pay_decrypt_keys.google_pay_root_signing_keys.clone()) + .await?; + + Ok(value.transition_state(|_| Self { + google_pay_root_signing_keys, + })) + } +} + #[async_trait::async_trait] impl SecretsHandler for settings::ApplepayMerchantConfigs { async fn convert_to_raw_secret( @@ -420,6 +438,20 @@ pub(crate) async fn fetch_raw_secrets( None }; + #[allow(clippy::expect_used)] + let google_pay_decrypt_keys = if let Some(google_pay_keys) = conf.google_pay_decrypt_keys { + Some( + settings::GooglePayDecryptConfig::convert_to_raw_secret( + google_pay_keys, + secret_management_client, + ) + .await + .expect("Failed to decrypt google pay decrypt configs"), + ) + } else { + None + }; + #[allow(clippy::expect_used)] let applepay_merchant_configs = settings::ApplepayMerchantConfigs::convert_to_raw_secret( conf.applepay_merchant_configs, @@ -512,6 +544,7 @@ pub(crate) async fn fetch_raw_secrets( payouts: conf.payouts, applepay_decrypt_keys, paze_decrypt_keys, + google_pay_decrypt_keys, multiple_api_version_supported_connectors: conf.multiple_api_version_supported_connectors, applepay_merchant_configs, lock_settings: conf.lock_settings, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index fa3d004aada..d721a4db967 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -99,6 +99,7 @@ pub struct Settings { pub payout_method_filters: ConnectorFilters, pub applepay_decrypt_keys: SecretStateContainer, pub paze_decrypt_keys: Option>, + pub google_pay_decrypt_keys: Option>, pub multiple_api_version_supported_connectors: MultipleApiVersionSupportedConnectors, pub applepay_merchant_configs: SecretStateContainer, pub lock_settings: LockSettings, @@ -758,6 +759,11 @@ pub struct PazeDecryptConfig { pub paze_private_key_passphrase: Secret, } +#[derive(Debug, Deserialize, Clone)] +pub struct GooglePayDecryptConfig { + pub google_pay_root_signing_keys: Secret, +} + #[derive(Debug, Deserialize, Clone, Default)] #[serde(default)] pub struct LockerBasedRecipientConnectorList { @@ -911,6 +917,11 @@ impl Settings { .map(|x| x.get_inner().validate()) .transpose()?; + self.google_pay_decrypt_keys + .as_ref() + .map(|x| x.get_inner().validate()) + .transpose()?; + self.key_manager.get_inner().validate()?; #[cfg(feature = "email")] self.email diff --git a/crates/router/src/configs/validations.rs b/crates/router/src/configs/validations.rs index 7040998ccf0..11aa4e0cf51 100644 --- a/crates/router/src/configs/validations.rs +++ b/crates/router/src/configs/validations.rs @@ -257,6 +257,21 @@ impl super::settings::PazeDecryptConfig { } } +impl super::settings::GooglePayDecryptConfig { + pub fn validate(&self) -> Result<(), ApplicationError> { + use common_utils::fp_utils::when; + + when( + self.google_pay_root_signing_keys.is_default_or_empty(), + || { + Err(ApplicationError::InvalidConfigurationValueError( + "google_pay_root_signing_keys must not be empty".into(), + )) + }, + ) + } +} + impl super::settings::KeyManagerConfig { pub fn validate(&self) -> Result<(), ApplicationError> { use common_utils::fp_utils::when; diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/router/src/connector/braintree/transformers.rs index 00624ce0f9b..5dd6db5aa24 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/router/src/connector/braintree/transformers.rs @@ -1598,6 +1598,9 @@ impl types::PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Braintree"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Braintree"))? + } }, transaction: transaction_body, }, @@ -1699,6 +1702,9 @@ fn get_braintree_redirect_form( types::PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Braintree"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Braintree"))? + } }, bin: match card_details { domain::PaymentMethodData::Card(card_details) => { diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index cd758fffb69..bb38ef74836 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -307,6 +307,9 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme types::PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Checkout"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Checkout"))? + } }, })), domain::WalletData::ApplePay(_) => { @@ -336,6 +339,9 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme types::PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Checkout"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Checkout"))? + } } } domain::WalletData::AliPayQr(_) diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index cd8ba814eb7..7e50f7f40e6 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -729,6 +729,9 @@ impl TryFrom<&types::PaymentsCompleteAuthorizeRouterData> for Pay3dsRequest { types::PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Payme"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(unimplemented_payment_method!("Google Pay", "Payme"))? + } }; Ok(Self { buyer_email, diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 48736c00e7d..60852779884 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1840,6 +1840,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent types::PaymentMethodToken::PazeDecrypt(_) => { Err(crate::unimplemented_payment_method!("Paze", "Stripe"))? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(crate::unimplemented_payment_method!("Google Pay", "Stripe"))? + } }; Some(StripePaymentMethodData::Wallet( StripeWallet::ApplepayPayment(ApplepayPayment { diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 96321d09794..b4c1c1c2c03 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -244,6 +244,60 @@ pub enum PazeDecryptionError { CertificateParsingFailed, } +#[derive(Debug, thiserror::Error)] +pub enum GooglePayDecryptionError { + #[error("Recipient ID not found")] + RecipientIdNotFound, + #[error("Invalid expiration time")] + InvalidExpirationTime, + #[error("Failed to base64 decode input data")] + Base64DecodingFailed, + #[error("Failed to decrypt input data")] + DecryptionFailed, + #[error("Failed to deserialize input data")] + DeserializationFailed, + #[error("Certificate parsing failed")] + CertificateParsingFailed, + #[error("Key deserialization failure")] + KeyDeserializationFailed, + #[error("Failed to derive a shared ephemeral key")] + DerivingSharedEphemeralKeyFailed, + #[error("Failed to derive a shared secret key")] + DerivingSharedSecretKeyFailed, + #[error("Failed to parse the tag")] + ParsingTagError, + #[error("HMAC verification failed")] + HmacVerificationFailed, + #[error("Failed to derive Elliptic Curve key")] + DerivingEcKeyFailed, + #[error("Failed to Derive Public key")] + DerivingPublicKeyFailed, + #[error("Failed to Derive Elliptic Curve group")] + DerivingEcGroupFailed, + #[error("Failed to allocate memory for big number")] + BigNumAllocationFailed, + #[error("Failed to get the ECDSA signature")] + EcdsaSignatureFailed, + #[error("Failed to verify the signature")] + SignatureVerificationFailed, + #[error("Invalid signature is provided")] + InvalidSignature, + #[error("Failed to parse the Signed Key")] + SignedKeyParsingFailure, + #[error("The Signed Key is expired")] + SignedKeyExpired, + #[error("Failed to parse the ECDSA signature")] + EcdsaSignatureParsingFailed, + #[error("Invalid intermediate signature is provided")] + InvalidIntermediateSignature, + #[error("Invalid protocol version")] + InvalidProtocolVersion, + #[error("Decrypted Token has expired")] + DecryptedTokenExpired, + #[error("Failed to parse the given value")] + ParsingFailed, +} + #[cfg(feature = "detailed_errors")] pub mod error_stack_parsing { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e9d3c013789..5b8988dace0 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3080,7 +3080,61 @@ where paze_decrypted_data, )))) } - _ => Ok(None), + TokenizationAction::DecryptGooglePayToken(payment_processing_details) => { + let google_pay_data = match payment_data.get_payment_method_data() { + Some(domain::PaymentMethodData::Wallet(domain::WalletData::GooglePay( + wallet_data, + ))) => { + let decryptor = helpers::GooglePayTokenDecryptor::new( + payment_processing_details + .google_pay_root_signing_keys + .clone(), + payment_processing_details.google_pay_recipient_id.clone(), + payment_processing_details.google_pay_private_key.clone(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to create google pay token decryptor")?; + + // should_verify_token is set to false to disable verification of token + Some( + decryptor + .decrypt_token(wallet_data.tokenization_data.token.clone(), false) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to decrypt google pay token")?, + ) + } + Some(payment_method_data) => { + logger::info!( + "Invalid payment_method_data found for Google Pay Decrypt Flow: {:?}", + payment_method_data.get_payment_method() + ); + None + } + None => { + logger::info!("No payment_method_data found for Google Pay Decrypt Flow"); + None + } + }; + + let google_pay_predecrypt = google_pay_data + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("failed to get GooglePayDecryptedData in response")?; + + Ok(Some(PaymentMethodToken::GooglePayDecrypt(Box::new( + google_pay_predecrypt, + )))) + } + TokenizationAction::ConnectorToken(_) => { + logger::info!("Invalid tokenization action found for decryption flow: ConnectorToken",); + Ok(None) + } + token_action => { + logger::info!( + "Invalid tokenization action found for decryption flow: {:?}", + token_action + ); + Ok(None) + } } } @@ -4052,6 +4106,64 @@ fn check_apple_pay_metadata( }) } +fn get_google_pay_connector_wallet_details( + state: &SessionState, + merchant_connector_account: &helpers::MerchantConnectorAccountType, +) -> Option { + let google_pay_root_signing_keys = + state + .conf + .google_pay_decrypt_keys + .as_ref() + .map(|google_pay_keys| { + google_pay_keys + .get_inner() + .google_pay_root_signing_keys + .clone() + }); + match ( + google_pay_root_signing_keys, + merchant_connector_account.get_connector_wallets_details(), + ) { + (Some(google_pay_root_signing_keys), Some(wallet_details)) => { + let google_pay_wallet_details = wallet_details + .parse_value::( + "GooglePayWalletDetails", + ) + .map_err(|error| { + logger::warn!(?error, "Failed to Parse Value to GooglePayWalletDetails") + }); + + google_pay_wallet_details + .ok() + .map( + |google_pay_wallet_details| { + match google_pay_wallet_details + .google_pay + .provider_details { + api_models::payments::GooglePayProviderDetails::GooglePayMerchantDetails(merchant_details) => { + GooglePayPaymentProcessingDetails { + google_pay_private_key: merchant_details + .merchant_info + .tokenization_specification + .parameters + .private_key, + google_pay_root_signing_keys, + google_pay_recipient_id: merchant_details + .merchant_info + .tokenization_specification + .parameters + .recipient_id, + } + } + } + } + ) + } + _ => None, + } +} + fn is_payment_method_type_allowed_for_connector( current_pm_type: Option, pm_type_filter: Option, @@ -4066,6 +4178,7 @@ fn is_payment_method_type_allowed_for_connector( } } +#[allow(clippy::too_many_arguments)] async fn decide_payment_method_tokenize_action( state: &SessionState, connector_name: &str, @@ -4074,6 +4187,7 @@ async fn decide_payment_method_tokenize_action( is_connector_tokenization_enabled: bool, apple_pay_flow: Option, payment_method_type: Option, + merchant_connector_account: &helpers::MerchantConnectorAccountType, ) -> RouterResult { if let Some(storage_enums::PaymentMethodType::Paze) = payment_method_type { // Paze generates a one time use network token which should not be tokenized in the connector or router. @@ -4090,6 +4204,20 @@ async fn decide_payment_method_tokenize_action( None => Err(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to fetch Paze configs"), } + } else if let Some(storage_enums::PaymentMethodType::GooglePay) = payment_method_type { + let google_pay_details = + get_google_pay_connector_wallet_details(state, merchant_connector_account); + + match google_pay_details { + Some(wallet_details) => Ok(TokenizationAction::DecryptGooglePayToken(wallet_details)), + None => { + if is_connector_tokenization_enabled { + Ok(TokenizationAction::TokenizeInConnectorAndRouter) + } else { + Ok(TokenizationAction::TokenizeInRouter) + } + } + } } else { match pm_parent_token { None => Ok(match (is_connector_tokenization_enabled, apple_pay_flow) { @@ -4154,6 +4282,13 @@ pub struct PazePaymentProcessingDetails { pub paze_private_key_passphrase: Secret, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct GooglePayPaymentProcessingDetails { + pub google_pay_private_key: Secret, + pub google_pay_root_signing_keys: Secret, + pub google_pay_recipient_id: Option>, +} + #[derive(Clone, Debug)] pub enum TokenizationAction { TokenizeInRouter, @@ -4164,6 +4299,7 @@ pub enum TokenizationAction { DecryptApplePayToken(payments_api::PaymentProcessingDetails), TokenizeInConnectorAndApplepayPreDecrypt(payments_api::PaymentProcessingDetails), DecryptPazeToken(PazePaymentProcessingDetails), + DecryptGooglePayToken(GooglePayPaymentProcessingDetails), } #[cfg(feature = "v2")] @@ -4254,6 +4390,7 @@ where is_connector_tokenization_enabled, apple_pay_flow, payment_method_type, + merchant_connector_account, ) .await?; @@ -4312,6 +4449,11 @@ where TokenizationAction::DecryptPazeToken(paze_payment_processing_details) => { TokenizationAction::DecryptPazeToken(paze_payment_processing_details) } + TokenizationAction::DecryptGooglePayToken( + google_pay_payment_processing_details, + ) => { + TokenizationAction::DecryptGooglePayToken(google_pay_payment_processing_details) + } }; (payment_data.to_owned(), connector_tokenization_action) } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 0d7a3b57fc1..56667b0517f 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -47,6 +47,7 @@ use openssl::{ }; #[cfg(feature = "v2")] use redis_interface::errors::RedisError; +use ring::hmac; use router_env::{instrument, logger, tracing}; use uuid::Uuid; use x509_parser::parse_x509_certificate; @@ -5073,6 +5074,9 @@ async fn get_and_merge_apple_pay_metadata( paze: connector_wallets_details_optional .as_ref() .and_then(|d| d.paze.clone()), + google_pay: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.google_pay.clone()), } } api_models::payments::ApplepaySessionTokenMetadata::ApplePay(apple_pay_metadata) => { @@ -5091,6 +5095,9 @@ async fn get_and_merge_apple_pay_metadata( paze: connector_wallets_details_optional .as_ref() .and_then(|d| d.paze.clone()), + google_pay: connector_wallets_details_optional + .as_ref() + .and_then(|d| d.google_pay.clone()), } } }; @@ -5427,6 +5434,543 @@ impl ApplePayData { } } +pub(crate) const SENDER_ID: &[u8] = b"Google"; +pub(crate) const PROTOCOL: &str = "ECv2"; + +// Structs for keys and the main decryptor +pub struct GooglePayTokenDecryptor { + root_signing_keys: Vec, + recipient_id: Option>, + private_key: PKey, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EncryptedData { + signature: String, + intermediate_signing_key: IntermediateSigningKey, + protocol_version: GooglePayProtocolVersion, + #[serde(with = "common_utils::custom_serde::json_string")] + signed_message: GooglePaySignedMessage, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePaySignedMessage { + #[serde(with = "common_utils::Base64Serializer")] + encrypted_message: masking::Secret>, + #[serde(with = "common_utils::Base64Serializer")] + ephemeral_public_key: masking::Secret>, + #[serde(with = "common_utils::Base64Serializer")] + tag: masking::Secret>, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct IntermediateSigningKey { + signed_key: masking::Secret, + signatures: Vec>, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePaySignedKey { + key_value: masking::Secret, + key_expiration: String, +} + +#[derive(Debug, Clone, serde::Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GooglePayRootSigningKey { + key_value: masking::Secret, + key_expiration: String, + protocol_version: GooglePayProtocolVersion, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq)] +pub enum GooglePayProtocolVersion { + #[serde(rename = "ECv2")] + EcProtocolVersion2, +} + +// Check expiration date validity +fn check_expiration_date_is_valid( + expiration: &str, +) -> CustomResult { + let expiration_ms = expiration + .parse::() + .change_context(errors::GooglePayDecryptionError::InvalidExpirationTime)?; + // convert milliseconds to nanoseconds (1 millisecond = 1_000_000 nanoseconds) to create OffsetDateTime + let expiration_time = + time::OffsetDateTime::from_unix_timestamp_nanos(expiration_ms * 1_000_000) + .change_context(errors::GooglePayDecryptionError::InvalidExpirationTime)?; + let now = time::OffsetDateTime::now_utc(); + + Ok(expiration_time > now) +} + +// Construct little endian format of u32 +fn get_little_endian_format(number: u32) -> Vec { + number.to_le_bytes().to_vec() +} + +// Filter and parse the root signing keys based on protocol version and expiration time +fn filter_root_signing_keys( + root_signing_keys: Vec, +) -> CustomResult, errors::GooglePayDecryptionError> { + let filtered_root_signing_keys = root_signing_keys + .iter() + .filter(|key| { + key.protocol_version == GooglePayProtocolVersion::EcProtocolVersion2 + && matches!( + check_expiration_date_is_valid(&key.key_expiration).inspect_err( + |err| logger::warn!( + "Failed to check expirattion due to invalid format: {:?}", + err + ) + ), + Ok(true) + ) + }) + .cloned() + .collect::>(); + + logger::info!( + "Filtered {} out of {} root signing keys", + filtered_root_signing_keys.len(), + root_signing_keys.len() + ); + + Ok(filtered_root_signing_keys) +} + +impl GooglePayTokenDecryptor { + pub fn new( + root_keys: masking::Secret, + recipient_id: Option>, + private_key: masking::Secret, + ) -> CustomResult { + // base64 decode the private key + let decoded_key = BASE64_ENGINE + .decode(private_key.expose()) + .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; + // create a private key from the decoded key + let private_key = PKey::private_key_from_pkcs8(&decoded_key) + .change_context(errors::GooglePayDecryptionError::KeyDeserializationFailed) + .attach_printable("cannot convert private key from decode_key")?; + + // parse the root signing keys + let root_keys_vector: Vec = root_keys + .expose() + .parse_struct("GooglePayRootSigningKey") + .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; + + // parse and filter the root signing keys by protocol version + let filtered_root_signing_keys = filter_root_signing_keys(root_keys_vector)?; + + Ok(Self { + root_signing_keys: filtered_root_signing_keys, + recipient_id, + private_key, + }) + } + + // Decrypt the Google pay token + pub fn decrypt_token( + &self, + data: String, + should_verify_signature: bool, + ) -> CustomResult< + hyperswitch_domain_models::router_data::GooglePayDecryptedData, + errors::GooglePayDecryptionError, + > { + // parse the encrypted data + let encrypted_data: EncryptedData = data + .parse_struct("EncryptedData") + .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; + + // verify the signature if required + if should_verify_signature { + self.verify_signature(&encrypted_data)?; + } + + let ephemeral_public_key = encrypted_data.signed_message.ephemeral_public_key.peek(); + let tag = encrypted_data.signed_message.tag.peek(); + let encrypted_message = encrypted_data.signed_message.encrypted_message.peek(); + + // derive the shared key + let shared_key = self.get_shared_key(ephemeral_public_key)?; + + // derive the symmetric encryption key and MAC key + let derived_key = self.derive_key(ephemeral_public_key, &shared_key)?; + // First 32 bytes for AES-256 and Remaining bytes for HMAC + let (symmetric_encryption_key, mac_key) = derived_key + .split_at_checked(32) + .ok_or(errors::GooglePayDecryptionError::ParsingFailed)?; + + // verify the HMAC of the message + self.verify_hmac(mac_key, tag, encrypted_message)?; + + // decrypt the message + let decrypted = self.decrypt_message(symmetric_encryption_key, encrypted_message)?; + + // parse the decrypted data + let decrypted_data: hyperswitch_domain_models::router_data::GooglePayDecryptedData = + decrypted + .parse_struct("GooglePayDecryptedData") + .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; + + // check the expiration date of the decrypted data + if matches!( + check_expiration_date_is_valid(&decrypted_data.message_expiration), + Ok(true) + ) { + Ok(decrypted_data) + } else { + Err(errors::GooglePayDecryptionError::DecryptedTokenExpired.into()) + } + } + + // Verify the signature of the token + fn verify_signature( + &self, + encrypted_data: &EncryptedData, + ) -> CustomResult<(), errors::GooglePayDecryptionError> { + // check the protocol version + if encrypted_data.protocol_version != GooglePayProtocolVersion::EcProtocolVersion2 { + return Err(errors::GooglePayDecryptionError::InvalidProtocolVersion.into()); + } + + // verify the intermediate signing key + self.verify_intermediate_signing_key(encrypted_data)?; + // validate and fetch the signed key + let signed_key = self.validate_signed_key(&encrypted_data.intermediate_signing_key)?; + // verify the signature of the token + self.verify_message_signature(encrypted_data, &signed_key) + } + + // Verify the intermediate signing key + fn verify_intermediate_signing_key( + &self, + encrypted_data: &EncryptedData, + ) -> CustomResult<(), errors::GooglePayDecryptionError> { + let mut signatrues: Vec = Vec::new(); + + // decode and parse the signatures + for signature in encrypted_data.intermediate_signing_key.signatures.iter() { + let signature = BASE64_ENGINE + .decode(signature.peek()) + .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; + let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature) + .change_context(errors::GooglePayDecryptionError::EcdsaSignatureParsingFailed)?; + signatrues.push(ecdsa_signature); + } + + // get the sender id i.e. Google + let sender_id = String::from_utf8(SENDER_ID.to_vec()) + .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; + + // construct the signed data + let signed_data = self.construct_signed_data_for_intermediate_signing_key_verification( + &sender_id, + PROTOCOL, + encrypted_data.intermediate_signing_key.signed_key.peek(), + )?; + + // check if any of the signatures are valid for any of the root signing keys + for key in self.root_signing_keys.iter() { + // decode and create public key + let public_key = self + .load_public_key(key.key_value.peek()) + .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?; + // fetch the ec key from public key + let ec_key = public_key + .ec_key() + .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?; + + // hash the signed data + let message_hash = openssl::sha::sha256(&signed_data); + + // verify if any of the signatures is valid against the given key + for signature in signatrues.iter() { + let result = signature.verify(&message_hash, &ec_key).change_context( + errors::GooglePayDecryptionError::SignatureVerificationFailed, + )?; + + if result { + return Ok(()); + } + } + } + + Err(errors::GooglePayDecryptionError::InvalidIntermediateSignature.into()) + } + + // Construct signed data for intermediate signing key verification + fn construct_signed_data_for_intermediate_signing_key_verification( + &self, + sender_id: &str, + protocol_version: &str, + signed_key: &str, + ) -> CustomResult, errors::GooglePayDecryptionError> { + let length_of_sender_id = u32::try_from(sender_id.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + let length_of_protocol_version = u32::try_from(protocol_version.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + let length_of_signed_key = u32::try_from(signed_key.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + + let mut signed_data: Vec = Vec::new(); + signed_data.append(&mut get_little_endian_format(length_of_sender_id)); + signed_data.append(&mut sender_id.as_bytes().to_vec()); + signed_data.append(&mut get_little_endian_format(length_of_protocol_version)); + signed_data.append(&mut protocol_version.as_bytes().to_vec()); + signed_data.append(&mut get_little_endian_format(length_of_signed_key)); + signed_data.append(&mut signed_key.as_bytes().to_vec()); + + Ok(signed_data) + } + + // Validate and parse signed key + fn validate_signed_key( + &self, + intermediate_signing_key: &IntermediateSigningKey, + ) -> CustomResult { + let signed_key: GooglePaySignedKey = intermediate_signing_key + .signed_key + .clone() + .expose() + .parse_struct("GooglePaySignedKey") + .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?; + if !matches!( + check_expiration_date_is_valid(&signed_key.key_expiration), + Ok(true) + ) { + return Err(errors::GooglePayDecryptionError::SignedKeyExpired)?; + } + Ok(signed_key) + } + + // Verify the signed message + fn verify_message_signature( + &self, + encrypted_data: &EncryptedData, + signed_key: &GooglePaySignedKey, + ) -> CustomResult<(), errors::GooglePayDecryptionError> { + // create a public key from the intermediate signing key + let public_key = self.load_public_key(signed_key.key_value.peek())?; + // base64 decode the signature + let signature = BASE64_ENGINE + .decode(&encrypted_data.signature) + .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; + + // parse the signature using ECDSA + let ecdsa_signature = openssl::ecdsa::EcdsaSig::from_der(&signature) + .change_context(errors::GooglePayDecryptionError::EcdsaSignatureFailed)?; + + // get the EC key from the public key + let ec_key = public_key + .ec_key() + .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?; + + // get the sender id i.e. Google + let sender_id = String::from_utf8(SENDER_ID.to_vec()) + .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; + + // serialize the signed message to string + let signed_message = serde_json::to_string(&encrypted_data.signed_message) + .change_context(errors::GooglePayDecryptionError::SignedKeyParsingFailure)?; + + // construct the signed data + let signed_data = self.construct_signed_data_for_signature_verification( + &sender_id, + PROTOCOL, + &signed_message, + )?; + + // hash the signed data + let message_hash = openssl::sha::sha256(&signed_data); + + // verify the signature + let result = ecdsa_signature + .verify(&message_hash, &ec_key) + .change_context(errors::GooglePayDecryptionError::SignatureVerificationFailed)?; + + if result { + Ok(()) + } else { + Err(errors::GooglePayDecryptionError::InvalidSignature)? + } + } + + // Fetch the public key + fn load_public_key( + &self, + key: &str, + ) -> CustomResult, errors::GooglePayDecryptionError> { + // decode the base64 string + let der_data = BASE64_ENGINE + .decode(key) + .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; + + // parse the DER-encoded data as an EC public key + let ec_key = openssl::ec::EcKey::public_key_from_der(&der_data) + .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?; + + // wrap the EC key in a PKey (a more general-purpose public key type in OpenSSL) + let public_key = PKey::from_ec_key(ec_key) + .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?; + + Ok(public_key) + } + + // Construct signed data for signature verification + fn construct_signed_data_for_signature_verification( + &self, + sender_id: &str, + protocol_version: &str, + signed_key: &str, + ) -> CustomResult, errors::GooglePayDecryptionError> { + let recipient_id = self + .recipient_id + .clone() + .ok_or(errors::GooglePayDecryptionError::RecipientIdNotFound)? + .expose(); + let length_of_sender_id = u32::try_from(sender_id.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + let length_of_recipient_id = u32::try_from(recipient_id.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + let length_of_protocol_version = u32::try_from(protocol_version.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + let length_of_signed_key = u32::try_from(signed_key.len()) + .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; + + let mut signed_data: Vec = Vec::new(); + signed_data.append(&mut get_little_endian_format(length_of_sender_id)); + signed_data.append(&mut sender_id.as_bytes().to_vec()); + signed_data.append(&mut get_little_endian_format(length_of_recipient_id)); + signed_data.append(&mut recipient_id.as_bytes().to_vec()); + signed_data.append(&mut get_little_endian_format(length_of_protocol_version)); + signed_data.append(&mut protocol_version.as_bytes().to_vec()); + signed_data.append(&mut get_little_endian_format(length_of_signed_key)); + signed_data.append(&mut signed_key.as_bytes().to_vec()); + + Ok(signed_data) + } + + // Derive a shared key using ECDH + fn get_shared_key( + &self, + ephemeral_public_key_bytes: &[u8], + ) -> CustomResult, errors::GooglePayDecryptionError> { + let group = openssl::ec::EcGroup::from_curve_name(openssl::nid::Nid::X9_62_PRIME256V1) + .change_context(errors::GooglePayDecryptionError::DerivingEcGroupFailed)?; + + let mut big_num_context = openssl::bn::BigNumContext::new() + .change_context(errors::GooglePayDecryptionError::BigNumAllocationFailed)?; + + let ec_key = openssl::ec::EcPoint::from_bytes( + &group, + ephemeral_public_key_bytes, + &mut big_num_context, + ) + .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?; + + // create an ephemeral public key from the given bytes + let ephemeral_public_key = openssl::ec::EcKey::from_public_key(&group, &ec_key) + .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?; + + // wrap the public key in a PKey + let ephemeral_pkey = PKey::from_ec_key(ephemeral_public_key) + .change_context(errors::GooglePayDecryptionError::DerivingPublicKeyFailed)?; + + // perform ECDH to derive the shared key + let mut deriver = Deriver::new(&self.private_key) + .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?; + + deriver + .set_peer(&ephemeral_pkey) + .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?; + + let shared_key = deriver + .derive_to_vec() + .change_context(errors::GooglePayDecryptionError::DerivingSharedSecretKeyFailed)?; + + Ok(shared_key) + } + + // Derive symmetric key and MAC key using HKDF + fn derive_key( + &self, + ephemeral_public_key_bytes: &[u8], + shared_key: &[u8], + ) -> CustomResult, errors::GooglePayDecryptionError> { + // concatenate ephemeral public key and shared key + let input_key_material = [ephemeral_public_key_bytes, shared_key].concat(); + + // initialize HKDF with SHA-256 as the hash function + // Salt is not provided as per the Google Pay documentation + // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#encrypt-spec + let hkdf: ::hkdf::Hkdf = ::hkdf::Hkdf::new(None, &input_key_material); + + // derive 64 bytes for the output key (symmetric encryption + MAC key) + let mut output_key = vec![0u8; 64]; + hkdf.expand(SENDER_ID, &mut output_key).map_err(|err| { + logger::error!( + "Failed to derive the shared ephemeral key for Google Pay decryption flow: {:?}", + err + ); + report!(errors::GooglePayDecryptionError::DerivingSharedEphemeralKeyFailed) + })?; + + Ok(output_key) + } + + // Verify the Hmac key + // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#encrypt-spec + fn verify_hmac( + &self, + mac_key: &[u8], + tag: &[u8], + encrypted_message: &[u8], + ) -> CustomResult<(), errors::GooglePayDecryptionError> { + let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, mac_key); + hmac::verify(&hmac_key, encrypted_message, tag) + .change_context(errors::GooglePayDecryptionError::HmacVerificationFailed) + } + + // Method to decrypt the AES-GCM encrypted message + fn decrypt_message( + &self, + symmetric_key: &[u8], + encrypted_message: &[u8], + ) -> CustomResult, errors::GooglePayDecryptionError> { + //initialization vector IV is typically used in AES-GCM (Galois/Counter Mode) encryption for randomizing the encryption process. + // zero iv is being passed as specified in Google Pay documentation + // https://developers.google.com/pay/api/android/guides/resources/payment-data-cryptography#decrypt-token + let iv = [0u8; 16]; + + // extract the tag from the end of the encrypted message + let tag = encrypted_message + .get(encrypted_message.len() - 16..) + .ok_or(errors::GooglePayDecryptionError::ParsingTagError)?; + + // decrypt the message using AES-256-CTR + let cipher = Cipher::aes_256_ctr(); + let decrypted_data = decrypt_aead( + cipher, + symmetric_key, + Some(&iv), + &[], + encrypted_message, + tag, + ) + .change_context(errors::GooglePayDecryptionError::DecryptionFailed)?; + + Ok(decrypted_data) + } +} + pub fn decrypt_paze_token( paze_wallet_data: PazeWalletData, paze_private_key: masking::Secret, diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 84f848ef0ab..ea81b8cb16f 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -139,6 +139,11 @@ where message: "Paze Decrypt token is not supported".to_string(), })? } + types::PaymentMethodToken::GooglePayDecrypt(_) => { + Err(errors::ApiErrorResponse::NotSupported { + message: "Google Pay Decrypt token is not supported".to_string(), + })? + } }; Some((connector_name, token)) } else { diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 5baca7bb55a..c11d54fc298 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -44,7 +44,8 @@ pub use hyperswitch_domain_models::{ router_data::{ AccessToken, AdditionalPaymentMethodConnectorResponse, ApplePayCryptogramData, ApplePayPredecryptData, ConnectorAuthType, ConnectorResponseData, ErrorResponse, - PaymentMethodBalance, PaymentMethodToken, RecurringMandatePaymentData, RouterData, + GooglePayDecryptedData, GooglePayPaymentMethodDetails, PaymentMethodBalance, + PaymentMethodToken, RecurringMandatePaymentData, RouterData, }, router_data_v2::{ AccessTokenFlowData, DisputesFlowData, ExternalAuthenticationFlowData, FilesFlowData, From 6f90b93cee6eb5fb688750b940ea884af8b1caa3 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Wed, 5 Feb 2025 17:44:40 +0530 Subject: [PATCH 031/133] fix(connector): [BOA] throw unsupported error incase of 3DS cards and limit administrative area length to 20 characters (#7174) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../connectors/bankofamerica/transformers.rs | 41 ++++++++-- .../e2e/configs/Payment/BankOfAmerica.js | 82 ++++--------------- 2 files changed, 50 insertions(+), 73 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index b33555a7c55..a0868d688eb 100644 --- a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -510,17 +510,26 @@ fn build_bill_to( country: None, email: email.clone(), }; + Ok(address_details .and_then(|addr| { - addr.address.as_ref().map(|addr| BillTo { - first_name: addr.first_name.clone(), - last_name: addr.last_name.clone(), - address1: addr.line1.clone(), - locality: addr.city.clone(), - administrative_area: addr.to_state_code_as_optional().ok().flatten(), - postal_code: addr.zip.clone(), - country: addr.country, - email, + addr.address.as_ref().map(|addr| { + let administrative_area = addr.to_state_code_as_optional().unwrap_or_else(|_| { + addr.state + .clone() + .map(|state| Secret::new(format!("{:.20}", state.expose()))) + }); + + BillTo { + first_name: addr.first_name.clone(), + last_name: addr.last_name.clone(), + address1: addr.line1.clone(), + locality: addr.city.clone(), + administrative_area, + postal_code: addr.zip.clone(), + country: addr.country, + email, + } }) }) .unwrap_or(default_address)) @@ -813,6 +822,13 @@ impl hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "BankOfAmerica", + })? + }; + let email = item.router_data.request.get_email()?; let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); @@ -2242,6 +2258,13 @@ impl hyperswitch_domain_models::payment_method_data::Card, ), ) -> Result { + if item.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "BankOfAmerica", + })? + }; + let order_information = OrderInformationWithBill::try_from(item)?; let client_reference_information = ClientReferenceInformation::from(item); let merchant_defined_information = diff --git a/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js b/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js index 308565e72fa..0c8a8803254 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js @@ -1,3 +1,5 @@ +import { getCustomExchange } from "./Modifiers"; + const successfulNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", @@ -112,7 +114,7 @@ export const connectorDetails = { }, }, }, - "3DSManualCapture": { + "3DSManualCapture": getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -122,14 +124,8 @@ export const connectorDetails = { customer_acceptance: null, setup_future_usage: "on_session", }, - Response: { - status: 200, - body: { - status: "requires_capture", - }, - }, - }, - "3DSAutoCapture": { + }), + "3DSAutoCapture": getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -139,13 +135,7 @@ export const connectorDetails = { customer_acceptance: null, setup_future_usage: "on_session", }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, - }, - }, + }), No3DSManualCapture: { Request: { payment_method: "card", @@ -301,7 +291,7 @@ export const connectorDetails = { }, }, }, - MandateSingleUse3DSAutoCapture: { + MandateSingleUse3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -310,14 +300,8 @@ export const connectorDetails = { currency: "USD", mandate_data: singleUseMandateData, }, - Response: { - status: 200, - body: { - status: "succeeded", - }, - }, - }, - MandateSingleUse3DSManualCapture: { + }), + MandateSingleUse3DSManualCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -326,13 +310,7 @@ export const connectorDetails = { currency: "USD", mandate_data: singleUseMandateData, }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, - }, - }, + }), MandateSingleUseNo3DSAutoCapture: { Request: { payment_method: "card", @@ -397,7 +375,7 @@ export const connectorDetails = { }, }, }, - MandateMultiUse3DSAutoCapture: { + MandateMultiUse3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -406,14 +384,8 @@ export const connectorDetails = { currency: "USD", mandate_data: multiUseMandateData, }, - Response: { - status: 200, - body: { - status: "requires_capture", - }, - }, - }, - MandateMultiUse3DSManualCapture: { + }), + MandateMultiUse3DSManualCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -422,13 +394,7 @@ export const connectorDetails = { currency: "USD", mandate_data: multiUseMandateData, }, - Response: { - status: 200, - body: { - status: "requires_capture", - }, - }, - }, + }), MITAutoCapture: { Request: {}, Response: { @@ -659,7 +625,7 @@ export const connectorDetails = { }, }, }, - PaymentMethodIdMandate3DSAutoCapture: { + PaymentMethodIdMandate3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -677,14 +643,8 @@ export const connectorDetails = { }, }, }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, - }, - }, - PaymentMethodIdMandate3DSManualCapture: { + }), + PaymentMethodIdMandate3DSManualCapture: getCustomExchange({ Request: { payment_method: "card", payment_method_data: { @@ -701,13 +661,7 @@ export const connectorDetails = { }, }, }, - Response: { - status: 200, - body: { - status: "requires_customer_action", - }, - }, - }, + }), }, pm_list: { PmListResponse: { From 899c207d5835ba39f5163d12c6f59aed39884359 Mon Sep 17 00:00:00 2001 From: Riddhiagrawal001 <50551695+Riddhiagrawal001@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:02:38 +0530 Subject: [PATCH 032/133] feat(users): custom role at profile read (#6875) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> --- crates/api_models/src/user_role/role.rs | 2 + crates/common_enums/src/enums.rs | 14 +- crates/diesel_models/src/query/role.rs | 137 ++++++---- crates/diesel_models/src/role.rs | 9 + crates/diesel_models/src/schema.rs | 2 + crates/diesel_models/src/schema_v2.rs | 2 + crates/router/src/core/user.rs | 1 + crates/router/src/core/user_role.rs | 3 + crates/router/src/core/user_role/role.rs | 232 +++++++++++------ crates/router/src/db/kafka_store.rs | 40 ++- crates/router/src/db/role.rs | 233 +++++++++--------- .../src/services/authorization/roles.rs | 9 +- crates/router/src/utils/user_role.rs | 38 ++- .../down.sql | 2 + .../up.sql | 2 + .../down.sql | 2 + .../up.sql | 3 + .../down.sql | 10 + .../up.sql | 13 + 19 files changed, 481 insertions(+), 273 deletions(-) create mode 100644 migrations/2024-10-17-073555_add-profile-id-to-roles/down.sql create mode 100644 migrations/2024-10-17-073555_add-profile-id-to-roles/up.sql create mode 100644 migrations/2024-10-17-123943_add-profile-enum-in-role-scope/down.sql create mode 100644 migrations/2024-10-17-123943_add-profile-enum-in-role-scope/up.sql create mode 100644 migrations/2024-12-18-061400_change-roles-index/down.sql create mode 100644 migrations/2024-12-18-061400_change-roles-index/up.sql diff --git a/crates/api_models/src/user_role/role.rs b/crates/api_models/src/user_role/role.rs index 7c877cd7477..6b8736d3e76 100644 --- a/crates/api_models/src/user_role/role.rs +++ b/crates/api_models/src/user_role/role.rs @@ -7,6 +7,7 @@ pub struct CreateRoleRequest { pub role_name: String, pub groups: Vec, pub role_scope: RoleScope, + pub entity_type: Option, } #[derive(Debug, serde::Deserialize, serde::Serialize)] @@ -21,6 +22,7 @@ pub struct RoleInfoWithGroupsResponse { pub groups: Vec, pub role_name: String, pub role_scope: RoleScope, + pub entity_type: EntityType, } #[derive(Debug, serde::Serialize)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 2f5249ded57..d9d76dbc53e 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2840,8 +2840,19 @@ pub enum TransactionType { #[serde(rename_all = "snake_case")] #[strum(serialize_all = "snake_case")] pub enum RoleScope { - Merchant, Organization, + Merchant, + Profile, +} + +impl From for EntityType { + fn from(role_scope: RoleScope) -> Self { + match role_scope { + RoleScope::Organization => Self::Organization, + RoleScope::Merchant => Self::Merchant, + RoleScope::Profile => Self::Profile, + } + } } /// Indicates the transaction status @@ -3296,6 +3307,7 @@ pub enum ApiVersion { serde::Serialize, strum::Display, strum::EnumString, + strum::EnumIter, ToSchema, Hash, )] diff --git a/crates/diesel_models/src/query/role.rs b/crates/diesel_models/src/query/role.rs index 2ab58ec2382..6fae9cda789 100644 --- a/crates/diesel_models/src/query/role.rs +++ b/crates/diesel_models/src/query/role.rs @@ -1,10 +1,12 @@ use async_bb8_diesel::AsyncRunQueryDsl; +use common_enums::EntityType; use common_utils::id_type; use diesel::{ associations::HasTable, debug_query, pg::Pg, result::Error as DieselError, BoolExpressionMethods, ExpressionMethods, QueryDsl, }; use error_stack::{report, ResultExt}; +use strum::IntoEnumIterator; use crate::{ enums::RoleScope, errors, query::generics, role::*, schema::roles::dsl, PgPooledConn, @@ -18,32 +20,23 @@ impl RoleNew { } impl Role { - pub async fn find_by_role_id(conn: &PgPooledConn, role_id: &str) -> StorageResult { - generics::generic_find_one::<::Table, _, _>( - conn, - dsl::role_id.eq(role_id.to_owned()), - ) - .await + fn get_entity_list( + current_entity: EntityType, + is_lineage_data_required: bool, + ) -> Vec { + is_lineage_data_required + .then(|| { + EntityType::iter() + .filter(|variant| *variant <= current_entity) + .collect() + }) + .unwrap_or(vec![current_entity]) } - // TODO: Remove once find_by_role_id_in_lineage is stable - pub async fn find_by_role_id_in_merchant_scope( - conn: &PgPooledConn, - role_id: &str, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> StorageResult { + pub async fn find_by_role_id(conn: &PgPooledConn, role_id: &str) -> StorageResult { generics::generic_find_one::<::Table, _, _>( conn, - dsl::role_id - .eq(role_id.to_owned()) - .and(dsl::tenant_id.eq(tenant_id.to_owned())) - .and( - dsl::merchant_id.eq(merchant_id.to_owned()).or(dsl::org_id - .eq(org_id.to_owned()) - .and(dsl::scope.eq(RoleScope::Organization))), - ), + dsl::role_id.eq(role_id.to_owned()), ) .await } @@ -53,6 +46,7 @@ impl Role { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> StorageResult { generics::generic_find_one::<::Table, _, _>( @@ -62,9 +56,14 @@ impl Role { .and(dsl::tenant_id.eq(tenant_id.to_owned())) .and(dsl::org_id.eq(org_id.to_owned())) .and( - dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id - .eq(merchant_id.to_owned()) - .and(dsl::scope.eq(RoleScope::Merchant))), + dsl::scope + .eq(RoleScope::Organization) + .or(dsl::merchant_id + .eq(merchant_id.to_owned()) + .and(dsl::scope.eq(RoleScope::Merchant))) + .or(dsl::profile_id + .eq(profile_id.to_owned()) + .and(dsl::scope.eq(RoleScope::Profile))), ), ) .await @@ -112,37 +111,13 @@ impl Role { .await } - pub async fn list_roles( - conn: &PgPooledConn, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> StorageResult> { - let predicate = dsl::tenant_id - .eq(tenant_id.to_owned()) - .and(dsl::org_id.eq(org_id.to_owned())) - .and( - dsl::scope.eq(RoleScope::Organization).or(dsl::merchant_id - .eq(merchant_id.to_owned()) - .and(dsl::scope.eq(RoleScope::Merchant))), - ); - - generics::generic_filter::<::Table, _, _, _>( - conn, - predicate, - None, - None, - Some(dsl::last_modified_at.asc()), - ) - .await - } - + //TODO: Remove once generic_list_roles_by_entity_type is stable pub async fn generic_roles_list_for_org( conn: &PgPooledConn, tenant_id: id_type::TenantId, org_id: id_type::OrganizationId, merchant_id: Option, - entity_type: Option, + entity_type: Option, limit: Option, ) -> StorageResult> { let mut query = ::table() @@ -183,4 +158,64 @@ impl Role { }, } } + + pub async fn generic_list_roles_by_entity_type( + conn: &PgPooledConn, + payload: ListRolesByEntityPayload, + is_lineage_data_required: bool, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + ) -> StorageResult> { + let mut query = ::table() + .into_boxed() + .filter(dsl::tenant_id.eq(tenant_id)) + .filter(dsl::org_id.eq(org_id)); + + match payload { + ListRolesByEntityPayload::Organization => { + let entity_in_vec = + Self::get_entity_list(EntityType::Organization, is_lineage_data_required); + query = query.filter(dsl::entity_type.eq_any(entity_in_vec)) + } + + ListRolesByEntityPayload::Merchant(merchant_id) => { + let entity_in_vec = + Self::get_entity_list(EntityType::Merchant, is_lineage_data_required); + query = query + .filter( + dsl::scope + .eq(RoleScope::Organization) + .or(dsl::merchant_id.eq(merchant_id)), + ) + .filter(dsl::entity_type.eq_any(entity_in_vec)) + } + + ListRolesByEntityPayload::Profile(merchant_id, profile_id) => { + let entity_in_vec = + Self::get_entity_list(EntityType::Profile, is_lineage_data_required); + query = query + .filter( + dsl::scope + .eq(RoleScope::Organization) + .or(dsl::scope + .eq(RoleScope::Merchant) + .and(dsl::merchant_id.eq(merchant_id.clone()))) + .or(dsl::profile_id.eq(profile_id)), + ) + .filter(dsl::entity_type.eq_any(entity_in_vec)) + } + }; + + router_env::logger::debug!(query = %debug_query::(&query).to_string()); + + match generics::db_metrics::track_database_call::( + query.get_results_async(conn), + generics::db_metrics::DatabaseOperation::Filter, + ) + .await + { + Ok(value) => Ok(value), + Err(err) => Err(report!(err)).change_context(errors::DatabaseError::Others), + } + } } diff --git a/crates/diesel_models/src/role.rs b/crates/diesel_models/src/role.rs index 16728801933..ba63dd61a0c 100644 --- a/crates/diesel_models/src/role.rs +++ b/crates/diesel_models/src/role.rs @@ -19,6 +19,7 @@ pub struct Role { pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, pub entity_type: enums::EntityType, + pub profile_id: Option, pub tenant_id: id_type::TenantId, } @@ -37,6 +38,7 @@ pub struct RoleNew { pub last_modified_at: PrimitiveDateTime, pub last_modified_by: String, pub entity_type: enums::EntityType, + pub profile_id: Option, pub tenant_id: id_type::TenantId, } @@ -75,3 +77,10 @@ impl From for RoleUpdateInternal { } } } + +#[derive(Clone, Debug)] +pub enum ListRolesByEntityPayload { + Profile(id_type::MerchantId, id_type::ProfileId), + Merchant(id_type::MerchantId), + Organization, +} diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 40343e44365..e748e5beca2 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -1332,6 +1332,8 @@ diesel::table! { #[max_length = 64] entity_type -> Varchar, #[max_length = 64] + profile_id -> Nullable, + #[max_length = 64] tenant_id -> Varchar, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 99d6139f310..07a76c13ae7 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -1279,6 +1279,8 @@ diesel::table! { #[max_length = 64] entity_type -> Varchar, #[max_length = 64] + profile_id -> Nullable, + #[max_length = 64] tenant_id -> Varchar, } } diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index 52d7d6a252e..ead0d4cd572 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -637,6 +637,7 @@ async fn handle_invitation( &request.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + &user_from_token.profile_id, user_from_token .tenant_id .as_ref() diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 19d91b14f01..2b09a4ae274 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -127,6 +127,7 @@ pub async fn update_user_role( &req.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + &user_from_token.profile_id, user_from_token .tenant_id .as_ref() @@ -551,6 +552,7 @@ pub async fn delete_user_role( &role_to_be_deleted.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + &user_from_token.profile_id, user_from_token .tenant_id .as_ref() @@ -625,6 +627,7 @@ pub async fn delete_user_role( &role_to_be_deleted.role_id, &user_from_token.merchant_id, &user_from_token.org_id, + &user_from_token.profile_id, user_from_token .tenant_id .as_ref() diff --git a/crates/router/src/core/user_role/role.rs b/crates/router/src/core/user_role/role.rs index ca4c062244c..34f0e8187ca 100644 --- a/crates/router/src/core/user_role/role.rs +++ b/crates/router/src/core/user_role/role.rs @@ -1,9 +1,9 @@ -use std::collections::HashSet; +use std::{cmp, collections::HashSet}; use api_models::user_role::role as role_api; -use common_enums::{EntityType, ParentGroup, PermissionGroup, RoleScope}; +use common_enums::{EntityType, ParentGroup, PermissionGroup}; use common_utils::generate_id_with_default_len; -use diesel_models::role::{RoleNew, RoleUpdate}; +use diesel_models::role::{ListRolesByEntityPayload, RoleNew, RoleUpdate}; use error_stack::{report, ResultExt}; use crate::{ @@ -65,6 +65,43 @@ pub async fn create_role( _req_state: ReqState, ) -> UserResponse { let now = common_utils::date_time::now(); + + let user_entity_type = user_from_token + .get_role_info_from_db(&state) + .await + .attach_printable("Invalid role_id in JWT")? + .get_entity_type(); + + let role_entity_type = req.entity_type.unwrap_or(EntityType::Merchant); + + if matches!(role_entity_type, EntityType::Organization) { + return Err(report!(UserErrors::InvalidRoleOperation)) + .attach_printable("User trying to create org level custom role"); + } + + // TODO: Remove in PR custom-role-write-pr + if matches!(role_entity_type, EntityType::Profile) { + return Err(report!(UserErrors::InvalidRoleOperation)) + .attach_printable("User trying to create profile level custom role"); + } + + let requestor_entity_from_role_scope = EntityType::from(req.role_scope); + + if requestor_entity_from_role_scope < role_entity_type { + return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!( + "User is trying to create role of type {} and scope {}", + role_entity_type, requestor_entity_from_role_scope + )); + } + let max_from_scope_and_entity = cmp::max(requestor_entity_from_role_scope, role_entity_type); + + if user_entity_type < max_from_scope_and_entity { + return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!( + "{} is trying to create of scope {} and of type {}", + user_entity_type, requestor_entity_from_role_scope, role_entity_type + )); + } + let role_name = RoleName::new(req.role_name)?; utils::user_role::validate_role_groups(&req.groups)?; @@ -77,33 +114,38 @@ pub async fn create_role( .tenant_id .as_ref() .unwrap_or(&state.tenant.tenant_id), + &user_from_token.profile_id, + &role_entity_type, ) .await?; - let user_role_info = user_from_token.get_role_info_from_db(&state).await?; - - if matches!(req.role_scope, RoleScope::Organization) - && user_role_info.get_entity_type() < EntityType::Organization - { - return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable( - "User does not have sufficient privileges to perform organization-level role operation", - ); - } + let (org_id, merchant_id, profile_id) = match role_entity_type { + EntityType::Organization | EntityType::Tenant => { + (user_from_token.org_id, user_from_token.merchant_id, None) + } + EntityType::Merchant => (user_from_token.org_id, user_from_token.merchant_id, None), + EntityType::Profile => ( + user_from_token.org_id, + user_from_token.merchant_id, + Some(user_from_token.profile_id), + ), + }; let role = state .global_store .insert_role(RoleNew { role_id: generate_id_with_default_len("role"), role_name: role_name.get_role_name(), - merchant_id: user_from_token.merchant_id, - org_id: user_from_token.org_id, + merchant_id, + org_id, groups: req.groups, scope: req.role_scope, - entity_type: EntityType::Merchant, + entity_type: role_entity_type, created_by: user_from_token.user_id.clone(), last_modified_by: user_from_token.user_id, created_at: now, last_modified_at: now, + profile_id, tenant_id: user_from_token.tenant_id.unwrap_or(state.tenant.tenant_id), }) .await @@ -115,6 +157,7 @@ pub async fn create_role( role_id: role.role_id, role_name: role.role_name, role_scope: role.scope, + entity_type: role.entity_type, }, )) } @@ -146,6 +189,7 @@ pub async fn get_role_with_groups( role_id: role.role_id, role_name: role_info.get_role_name().to_string(), role_scope: role_info.get_scope(), + entity_type: role_info.get_entity_type(), }, )) } @@ -207,6 +251,36 @@ pub async fn update_role( ) -> UserResponse { let role_name = req.role_name.map(RoleName::new).transpose()?; + let role_info = roles::RoleInfo::from_role_id_in_lineage( + &state, + role_id, + &user_from_token.merchant_id, + &user_from_token.org_id, + &user_from_token.profile_id, + user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id), + ) + .await + .to_not_found_response(UserErrors::InvalidRoleOperation)?; + + let user_role_info = user_from_token.get_role_info_from_db(&state).await?; + + let requested_entity_from_role_scope = EntityType::from(role_info.get_scope()); + let requested_role_entity_type = role_info.get_entity_type(); + let max_from_scope_and_entity = + cmp::max(requested_entity_from_role_scope, requested_role_entity_type); + + if user_role_info.get_entity_type() < max_from_scope_and_entity { + return Err(report!(UserErrors::InvalidRoleOperation)).attach_printable(format!( + "{} is trying to update of scope {} and of type {}", + user_role_info.get_entity_type(), + requested_entity_from_role_scope, + requested_role_entity_type + )); + } + if let Some(ref role_name) = role_name { utils::user_role::validate_role_name( &state, @@ -217,6 +291,8 @@ pub async fn update_role( .tenant_id .as_ref() .unwrap_or(&state.tenant.tenant_id), + &user_from_token.profile_id, + &role_info.get_entity_type(), ) .await?; } @@ -225,28 +301,6 @@ pub async fn update_role( utils::user_role::validate_role_groups(groups)?; } - let role_info = roles::RoleInfo::from_role_id_in_lineage( - &state, - role_id, - &user_from_token.merchant_id, - &user_from_token.org_id, - user_from_token - .tenant_id - .as_ref() - .unwrap_or(&state.tenant.tenant_id), - ) - .await - .to_not_found_response(UserErrors::InvalidRoleOperation)?; - - let user_role_info = user_from_token.get_role_info_from_db(&state).await?; - - if matches!(role_info.get_scope(), RoleScope::Organization) - && user_role_info.get_entity_type() != EntityType::Organization - { - return Err(report!(UserErrors::InvalidRoleOperation)) - .attach_printable("Non org admin user changing org level role"); - } - let updated_role = state .global_store .update_role_by_role_id( @@ -269,6 +323,7 @@ pub async fn update_role( role_id: updated_role.role_id, role_name: updated_role.role_name, role_scope: updated_role.scope, + entity_type: updated_role.entity_type, }, )) } @@ -296,40 +351,51 @@ pub async fn list_roles_with_info( .collect::>(); let user_role_entity = user_role_info.get_entity_type(); + let is_lineage_data_required = request.entity_type.is_none(); + let tenant_id = user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id) + .to_owned(); let custom_roles = match utils::user_role::get_min_entity(user_role_entity, request.entity_type)? { EntityType::Tenant | EntityType::Organization => state .global_store - .list_roles_for_org_by_parameters( - user_from_token - .tenant_id - .as_ref() - .unwrap_or(&state.tenant.tenant_id), - &user_from_token.org_id, - None, - request.entity_type, - None, + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Organization, + is_lineage_data_required, + tenant_id, + user_from_token.org_id, ) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get roles")?, EntityType::Merchant => state .global_store - .list_roles_for_org_by_parameters( - user_from_token - .tenant_id - .as_ref() - .unwrap_or(&state.tenant.tenant_id), - &user_from_token.org_id, - Some(&user_from_token.merchant_id), - request.entity_type, - None, + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Merchant(user_from_token.merchant_id), + is_lineage_data_required, + tenant_id, + user_from_token.org_id, + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get roles")?, + + EntityType::Profile => state + .global_store + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Profile( + user_from_token.merchant_id, + user_from_token.profile_id, + ), + is_lineage_data_required, + tenant_id, + user_from_token.org_id, ) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get roles")?, - // TODO: Populate this from Db function when support for profile id and profile level custom roles is added - EntityType::Profile => Vec::new(), }; role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from)); @@ -378,18 +444,21 @@ pub async fn list_roles_at_entity_level( .map(|(_, role_info)| role_info.clone()) .collect::>(); + let tenant_id = user_from_token + .tenant_id + .as_ref() + .unwrap_or(&state.tenant.tenant_id) + .to_owned(); + + let is_lineage_data_required = false; let custom_roles = match req.entity_type { EntityType::Tenant | EntityType::Organization => state .global_store - .list_roles_for_org_by_parameters( - user_from_token - .tenant_id - .as_ref() - .unwrap_or(&state.tenant.tenant_id), - &user_from_token.org_id, - None, - Some(req.entity_type), - None, + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Organization, + is_lineage_data_required, + tenant_id, + user_from_token.org_id, ) .await .change_context(UserErrors::InternalServerError) @@ -397,21 +466,30 @@ pub async fn list_roles_at_entity_level( EntityType::Merchant => state .global_store - .list_roles_for_org_by_parameters( - user_from_token - .tenant_id - .as_ref() - .unwrap_or(&state.tenant.tenant_id), - &user_from_token.org_id, - Some(&user_from_token.merchant_id), - Some(req.entity_type), - None, + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Merchant(user_from_token.merchant_id), + is_lineage_data_required, + tenant_id, + user_from_token.org_id, + ) + .await + .change_context(UserErrors::InternalServerError) + .attach_printable("Failed to get roles")?, + + EntityType::Profile => state + .global_store + .generic_list_roles_by_entity_type( + ListRolesByEntityPayload::Profile( + user_from_token.merchant_id, + user_from_token.profile_id, + ), + is_lineage_data_required, + tenant_id, + user_from_token.org_id, ) .await .change_context(UserErrors::InternalServerError) .attach_printable("Failed to get roles")?, - // TODO: Populate this from Db function when support for profile id and profile level custom roles is added - EntityType::Profile => Vec::new(), }; role_info_vec.extend(custom_roles.into_iter().map(roles::RoleInfo::from)); diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 22e2f10c71c..7d4f16ee893 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -3611,28 +3611,16 @@ impl RoleInterface for KafkaStore { self.diesel_store.find_role_by_role_id(role_id).await } - //TODO:Remove once find_by_role_id_in_lineage is stable - async fn find_role_by_role_id_in_merchant_scope( - &self, - role_id: &str, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult { - self.diesel_store - .find_role_by_role_id_in_merchant_scope(role_id, merchant_id, org_id, tenant_id) - .await - } - async fn find_role_by_role_id_in_lineage( &self, role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> CustomResult { self.diesel_store - .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id) + .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, profile_id, tenant_id) .await } @@ -3664,17 +3652,7 @@ impl RoleInterface for KafkaStore { self.diesel_store.delete_role_by_role_id(role_id).await } - async fn list_all_roles( - &self, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult, errors::StorageError> { - self.diesel_store - .list_all_roles(merchant_id, org_id, tenant_id) - .await - } - + //TODO: Remove once generic_list_roles_by_entity_type is stable async fn list_roles_for_org_by_parameters( &self, tenant_id: &id_type::TenantId, @@ -3687,6 +3665,18 @@ impl RoleInterface for KafkaStore { .list_roles_for_org_by_parameters(tenant_id, org_id, merchant_id, entity_type, limit) .await } + + async fn generic_list_roles_by_entity_type( + &self, + payload: diesel_models::role::ListRolesByEntityPayload, + is_lineage_data_required: bool, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + ) -> CustomResult, errors::StorageError> { + self.diesel_store + .generic_list_roles_by_entity_type(payload, is_lineage_data_required, tenant_id, org_id) + .await + } } #[async_trait::async_trait] diff --git a/crates/router/src/db/role.rs b/crates/router/src/db/role.rs index 1006c33aaa0..532a59f5149 100644 --- a/crates/router/src/db/role.rs +++ b/crates/router/src/db/role.rs @@ -1,6 +1,8 @@ -use common_enums::enums; use common_utils::id_type; -use diesel_models::role as storage; +use diesel_models::{ + enums::{EntityType, RoleScope}, + role as storage, +}; use error_stack::report; use router_env::{instrument, tracing}; @@ -23,20 +25,12 @@ pub trait RoleInterface { role_id: &str, ) -> CustomResult; - //TODO:Remove once find_by_role_id_in_lineage is stable - async fn find_role_by_role_id_in_merchant_scope( - &self, - role_id: &str, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult; - async fn find_role_by_role_id_in_lineage( &self, role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> CustomResult; @@ -58,21 +52,23 @@ pub trait RoleInterface { role_id: &str, ) -> CustomResult; - async fn list_all_roles( - &self, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult, errors::StorageError>; - + //TODO: Remove once generic_list_roles_by_entity_type is stable async fn list_roles_for_org_by_parameters( &self, tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, - entity_type: Option, + entity_type: Option, limit: Option, ) -> CustomResult, errors::StorageError>; + + async fn generic_list_roles_by_entity_type( + &self, + payload: storage::ListRolesByEntityPayload, + is_lineage_data_required: bool, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + ) -> CustomResult, errors::StorageError>; } #[async_trait::async_trait] @@ -99,41 +95,28 @@ impl RoleInterface for Store { .map_err(|error| report!(errors::StorageError::from(error))) } - //TODO:Remove once find_by_role_id_in_lineage is stable #[instrument(skip_all)] - async fn find_role_by_role_id_in_merchant_scope( + async fn find_role_by_role_id_in_lineage( &self, role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> CustomResult { let conn = connection::pg_connection_read(self).await?; - storage::Role::find_by_role_id_in_merchant_scope( + storage::Role::find_by_role_id_in_lineage( &conn, role_id, merchant_id, org_id, + profile_id, tenant_id, ) .await .map_err(|error| report!(errors::StorageError::from(error))) } - #[instrument(skip_all)] - async fn find_role_by_role_id_in_lineage( - &self, - role_id: &str, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; - storage::Role::find_by_role_id_in_lineage(&conn, role_id, merchant_id, org_id, tenant_id) - .await - .map_err(|error| report!(errors::StorageError::from(error))) - } - #[instrument(skip_all)] async fn find_by_role_id_org_id_tenant_id( &self, @@ -170,26 +153,14 @@ impl RoleInterface for Store { .map_err(|error| report!(errors::StorageError::from(error))) } - #[instrument(skip_all)] - async fn list_all_roles( - &self, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult, errors::StorageError> { - let conn = connection::pg_connection_read(self).await?; - storage::Role::list_roles(&conn, merchant_id, org_id, tenant_id) - .await - .map_err(|error| report!(errors::StorageError::from(error))) - } - + //TODO: Remove once generic_list_roles_by_entity_type is stable #[instrument(skip_all)] async fn list_roles_for_org_by_parameters( &self, tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, - entity_type: Option, + entity_type: Option, limit: Option, ) -> CustomResult, errors::StorageError> { let conn = connection::pg_connection_read(self).await?; @@ -204,6 +175,26 @@ impl RoleInterface for Store { .await .map_err(|error| report!(errors::StorageError::from(error))) } + + #[instrument(skip_all)] + async fn generic_list_roles_by_entity_type( + &self, + payload: storage::ListRolesByEntityPayload, + is_lineage_data_required: bool, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + ) -> CustomResult, errors::StorageError> { + let conn = connection::pg_connection_read(self).await?; + storage::Role::generic_list_roles_by_entity_type( + &conn, + payload, + is_lineage_data_required, + tenant_id, + org_id, + ) + .await + .map_err(|error| report!(errors::StorageError::from(error))) + } } #[async_trait::async_trait] @@ -234,6 +225,7 @@ impl RoleInterface for MockDb { created_at: role.created_at, last_modified_at: role.last_modified_at, last_modified_by: role.last_modified_by, + profile_id: role.profile_id, tenant_id: role.tenant_id, }; roles.push(role.clone()); @@ -257,38 +249,12 @@ impl RoleInterface for MockDb { ) } - // TODO: Remove once find_by_role_id_in_lineage is stable - async fn find_role_by_role_id_in_merchant_scope( - &self, - role_id: &str, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult { - let roles = self.roles.lock().await; - roles - .iter() - .find(|role| { - role.role_id == role_id - && (role.tenant_id == *tenant_id) - && (role.merchant_id == *merchant_id - || (role.org_id == *org_id && role.scope == enums::RoleScope::Organization)) - }) - .cloned() - .ok_or( - errors::StorageError::ValueNotFound(format!( - "No role available in merchant scope for role_id = {role_id}, \ - merchant_id = {merchant_id:?} and org_id = {org_id:?}" - )) - .into(), - ) - } - async fn find_role_by_role_id_in_lineage( &self, role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> CustomResult { let roles = self.roles.lock().await; @@ -298,9 +264,15 @@ impl RoleInterface for MockDb { role.role_id == role_id && (role.tenant_id == *tenant_id) && role.org_id == *org_id - && ((role.scope == enums::RoleScope::Organization) - || (role.merchant_id == *merchant_id - && role.scope == enums::RoleScope::Merchant)) + && ((role.scope == RoleScope::Organization) + || (role.merchant_id == *merchant_id && role.scope == RoleScope::Merchant) + || (role + .profile_id + .as_ref() + .is_some_and(|profile_id_from_role| { + profile_id_from_role == profile_id + && role.scope == RoleScope::Profile + }))) }) .cloned() .ok_or( @@ -382,43 +354,14 @@ impl RoleInterface for MockDb { Ok(roles.remove(role_index)) } - async fn list_all_roles( - &self, - merchant_id: &id_type::MerchantId, - org_id: &id_type::OrganizationId, - tenant_id: &id_type::TenantId, - ) -> CustomResult, errors::StorageError> { - let roles = self.roles.lock().await; - - let roles_list: Vec<_> = roles - .iter() - .filter(|role| { - role.tenant_id == *tenant_id - && (role.merchant_id == *merchant_id - || (role.org_id == *org_id - && role.scope == diesel_models::enums::RoleScope::Organization)) - }) - .cloned() - .collect(); - - if roles_list.is_empty() { - return Err(errors::StorageError::ValueNotFound(format!( - "No role found for merchant id = {:?} and org_id = {:?}", - merchant_id, org_id - )) - .into()); - } - - Ok(roles_list) - } - + //TODO: Remove once generic_list_roles_by_entity_type is stable #[instrument(skip_all)] async fn list_roles_for_org_by_parameters( &self, tenant_id: &id_type::TenantId, org_id: &id_type::OrganizationId, merchant_id: Option<&id_type::MerchantId>, - entity_type: Option, + entity_type: Option, limit: Option, ) -> CustomResult, errors::StorageError> { let roles = self.roles.lock().await; @@ -442,4 +385,72 @@ impl RoleInterface for MockDb { Ok(roles_list) } + + #[instrument(skip_all)] + async fn generic_list_roles_by_entity_type( + &self, + payload: storage::ListRolesByEntityPayload, + is_lineage_data_required: bool, + tenant_id: id_type::TenantId, + org_id: id_type::OrganizationId, + ) -> CustomResult, errors::StorageError> { + let roles = self.roles.lock().await; + let roles_list: Vec<_> = roles + .iter() + .filter(|role| match &payload { + storage::ListRolesByEntityPayload::Organization => { + let entity_in_vec = if is_lineage_data_required { + vec![ + EntityType::Organization, + EntityType::Merchant, + EntityType::Profile, + ] + } else { + vec![EntityType::Organization] + }; + + role.tenant_id == tenant_id + && role.org_id == org_id + && entity_in_vec.contains(&role.entity_type) + } + storage::ListRolesByEntityPayload::Merchant(merchant_id) => { + let entity_in_vec = if is_lineage_data_required { + vec![EntityType::Merchant, EntityType::Profile] + } else { + vec![EntityType::Merchant] + }; + + role.tenant_id == tenant_id + && role.org_id == org_id + && (role.scope == RoleScope::Organization + || role.merchant_id == *merchant_id) + && entity_in_vec.contains(&role.entity_type) + } + storage::ListRolesByEntityPayload::Profile(merchant_id, profile_id) => { + let entity_in_vec = [EntityType::Profile]; + + let matches_merchant = + role.merchant_id == *merchant_id && role.scope == RoleScope::Merchant; + + let matches_profile = + role.profile_id + .as_ref() + .is_some_and(|profile_id_from_role| { + profile_id_from_role == profile_id + && role.scope == RoleScope::Profile + }); + + role.tenant_id == tenant_id + && role.org_id == org_id + && (role.scope == RoleScope::Organization + || matches_merchant + || matches_profile) + && entity_in_vec.contains(&role.entity_type) + } + }) + .cloned() + .collect(); + + Ok(roles_list) + } } diff --git a/crates/router/src/services/authorization/roles.rs b/crates/router/src/services/authorization/roles.rs index df2a14a1a1a..c6b6946d15c 100644 --- a/crates/router/src/services/authorization/roles.rs +++ b/crates/router/src/services/authorization/roles.rs @@ -121,6 +121,7 @@ impl RoleInfo { role_id: &str, merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, + profile_id: &id_type::ProfileId, tenant_id: &id_type::TenantId, ) -> CustomResult { if let Some(role) = predefined_roles::PREDEFINED_ROLES.get(role_id) { @@ -128,7 +129,13 @@ impl RoleInfo { } else { state .global_store - .find_role_by_role_id_in_lineage(role_id, merchant_id, org_id, tenant_id) + .find_role_by_role_id_in_lineage( + role_id, + merchant_id, + org_id, + profile_id, + tenant_id, + ) .await .map(Self::from) } diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 7413e66070f..b3e51136193 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -4,6 +4,7 @@ use common_enums::{EntityType, PermissionGroup}; use common_utils::id_type; use diesel_models::{ enums::{UserRoleVersion, UserStatus}, + role::ListRolesByEntityPayload, user_role::{UserRole, UserRoleUpdate}, }; use error_stack::{report, Report, ResultExt}; @@ -49,6 +50,8 @@ pub async fn validate_role_name( merchant_id: &id_type::MerchantId, org_id: &id_type::OrganizationId, tenant_id: &id_type::TenantId, + profile_id: &id_type::ProfileId, + entity_type: &EntityType, ) -> UserResult<()> { let role_name_str = role_name.clone().get_role_name(); @@ -56,16 +59,37 @@ pub async fn validate_role_name( .iter() .any(|(_, role_info)| role_info.get_role_name() == role_name_str); - // TODO: Create and use find_by_role_name to make this efficient - let is_present_in_custom_roles = state + let entity_type_for_role = match entity_type { + EntityType::Tenant | EntityType::Organization => ListRolesByEntityPayload::Organization, + EntityType::Merchant => ListRolesByEntityPayload::Merchant(merchant_id.to_owned()), + EntityType::Profile => { + ListRolesByEntityPayload::Profile(merchant_id.to_owned(), profile_id.to_owned()) + } + }; + + let is_present_in_custom_role = match state .global_store - .list_all_roles(merchant_id, org_id, tenant_id) + .generic_list_roles_by_entity_type( + entity_type_for_role, + false, + tenant_id.to_owned(), + org_id.to_owned(), + ) .await - .change_context(UserErrors::InternalServerError)? - .iter() - .any(|role| role.role_name == role_name_str); + { + Ok(roles_list) => roles_list + .iter() + .any(|role| role.role_name == role_name_str), + Err(e) => { + if e.current_context().is_db_not_found() { + false + } else { + return Err(UserErrors::InternalServerError.into()); + } + } + }; - if is_present_in_predefined_roles || is_present_in_custom_roles { + if is_present_in_predefined_roles || is_present_in_custom_role { return Err(UserErrors::RoleNameAlreadyExists.into()); } diff --git a/migrations/2024-10-17-073555_add-profile-id-to-roles/down.sql b/migrations/2024-10-17-073555_add-profile-id-to-roles/down.sql new file mode 100644 index 00000000000..d611be2c3da --- /dev/null +++ b/migrations/2024-10-17-073555_add-profile-id-to-roles/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE roles DROP COLUMN IF EXISTS profile_id; \ No newline at end of file diff --git a/migrations/2024-10-17-073555_add-profile-id-to-roles/up.sql b/migrations/2024-10-17-073555_add-profile-id-to-roles/up.sql new file mode 100644 index 00000000000..b3873266fec --- /dev/null +++ b/migrations/2024-10-17-073555_add-profile-id-to-roles/up.sql @@ -0,0 +1,2 @@ +-- Your SQL goes here +ALTER TABLE roles ADD COLUMN IF NOT EXISTS profile_id VARCHAR(64); \ No newline at end of file diff --git a/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/down.sql b/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/down.sql new file mode 100644 index 00000000000..c7c9cbeb401 --- /dev/null +++ b/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +SELECT 1; \ No newline at end of file diff --git a/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/up.sql b/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/up.sql new file mode 100644 index 00000000000..6fd9b07fd50 --- /dev/null +++ b/migrations/2024-10-17-123943_add-profile-enum-in-role-scope/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TYPE "RoleScope" +ADD VALUE IF NOT EXISTS 'profile'; \ No newline at end of file diff --git a/migrations/2024-12-18-061400_change-roles-index/down.sql b/migrations/2024-12-18-061400_change-roles-index/down.sql new file mode 100644 index 00000000000..f59b8c87669 --- /dev/null +++ b/migrations/2024-12-18-061400_change-roles-index/down.sql @@ -0,0 +1,10 @@ +-- This file should undo anything in `up.sql` +CREATE UNIQUE INDEX role_name_org_id_org_scope_index ON roles (org_id, role_name) +WHERE + scope = 'organization'; + +CREATE UNIQUE INDEX role_name_merchant_id_merchant_scope_index ON roles (merchant_id, role_name) +WHERE + scope = 'merchant'; + +DROP INDEX IF EXISTS roles_merchant_org_index; \ No newline at end of file diff --git a/migrations/2024-12-18-061400_change-roles-index/up.sql b/migrations/2024-12-18-061400_change-roles-index/up.sql new file mode 100644 index 00000000000..08203559eb9 --- /dev/null +++ b/migrations/2024-12-18-061400_change-roles-index/up.sql @@ -0,0 +1,13 @@ +-- Your SQL goes here + +DROP INDEX IF EXISTS role_name_org_id_org_scope_index; + +DROP INDEX IF EXISTS role_name_merchant_id_merchant_scope_index; + +DROP INDEX IF EXISTS roles_merchant_org_index; + +CREATE INDEX roles_merchant_org_index ON roles ( + org_id, + merchant_id, + profile_id +); \ No newline at end of file From 8d8ebe9051675d8102c6f9ea887bb23751ea5724 Mon Sep 17 00:00:00 2001 From: Suman Maji <77887221+sumanmaji4@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:03:11 +0530 Subject: [PATCH 033/133] refactor(core): add recurring customer support for nomupay payouts. (#6687) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 10 + api-reference/openapi_spec.json | 20 ++ crates/api_models/src/payment_methods.rs | 104 ++++++- crates/api_models/src/payouts.rs | 6 + crates/diesel_models/src/payment_method.rs | 143 +++++++++- crates/hyperswitch_connectors/src/utils.rs | 20 ++ .../hyperswitch_domain_models/src/mandates.rs | 259 +++++++++++++++++- .../src/merchant_connector_account.rs | 10 +- .../src/payment_methods.rs | 248 ++++++++++++++++- .../src/router_request_types.rs | 1 + crates/router/src/core/payment_methods.rs | 8 +- .../router/src/core/payment_methods/cards.rs | 42 +-- crates/router/src/core/payments.rs | 14 +- crates/router/src/core/payments/helpers.rs | 79 +++--- .../payments/operations/payment_response.rs | 23 +- crates/router/src/core/payments/routing.rs | 2 +- .../router/src/core/payments/tokenization.rs | 59 ++-- crates/router/src/core/payouts.rs | 219 ++++++++++++++- crates/router/src/core/payouts/helpers.rs | 118 +++++--- .../router/src/core/payouts/transformers.rs | 1 + crates/router/src/core/payouts/validator.rs | 131 +++++++-- crates/router/src/core/utils.rs | 6 +- crates/router/src/core/webhooks/incoming.rs | 42 +-- crates/router/src/types/transformers.rs | 27 +- crates/router/tests/connectors/utils.rs | 1 + 25 files changed, 1355 insertions(+), 238 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 447010178de..bc593adb45b 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -16999,6 +16999,11 @@ "example": "+1", "nullable": true, "maxLength": 255 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } }, "additionalProperties": false @@ -17238,6 +17243,11 @@ "example": "Invalid card details", "nullable": true, "maxLength": 1024 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } }, "additionalProperties": false diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index fa631eee788..bce5ac3c8e8 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -20921,6 +20921,11 @@ "example": "+1", "nullable": true, "maxLength": 255 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } } }, @@ -21219,6 +21224,11 @@ "example": "Invalid card details", "nullable": true, "maxLength": 1024 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } }, "additionalProperties": false @@ -21865,6 +21875,11 @@ "example": "+1", "nullable": true, "maxLength": 255 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } } }, @@ -22077,6 +22092,11 @@ "example": "+1", "nullable": true, "maxLength": 255 + }, + "payout_method_id": { + "type": "string", + "description": "Identifier for payout method", + "nullable": true } } }, diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index 12bb97d9b7d..d15fdd74432 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -243,7 +243,8 @@ pub struct PaymentMethodMigrate { pub billing: Option, /// The connector mandate details of the payment method - pub connector_mandate_details: Option, + #[serde(deserialize_with = "deserialize_connector_mandate_details")] + pub connector_mandate_details: Option, // The CIT (customer initiated transaction) transaction id associated with the payment method pub network_transaction_id: Option, @@ -267,12 +268,22 @@ pub struct PaymentMethodMigrateResponse { pub network_transaction_id_migrated: Option, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct PaymentsMandateReference( pub HashMap, ); -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct PayoutsMandateReference( + pub HashMap, +); + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct PayoutsMandateReferenceRecord { + pub transfer_method_id: Option, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] pub struct PaymentsMandateReferenceRecord { pub connector_mandate_id: String, pub payment_method_type: Option, @@ -280,6 +291,80 @@ pub struct PaymentsMandateReferenceRecord { pub original_payment_authorized_currency: Option, } +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct CommonMandateReference { + pub payments: Option, + pub payouts: Option, +} + +impl From for PaymentsMandateReference { + fn from(common_mandate: CommonMandateReference) -> Self { + common_mandate.payments.unwrap_or_default() + } +} + +impl From for CommonMandateReference { + fn from(payments_reference: PaymentsMandateReference) -> Self { + Self { + payments: Some(payments_reference), + payouts: None, + } + } +} + +fn deserialize_connector_mandate_details<'de, D>( + deserializer: D, +) -> Result, D::Error> +where + D: serde::Deserializer<'de>, +{ + let value: Option = + as de::Deserialize>::deserialize(deserializer)?; + + let payments_data = value + .clone() + .map(|mut mandate_details| { + mandate_details + .as_object_mut() + .map(|obj| obj.remove("payouts")); + + serde_json::from_value::(mandate_details) + }) + .transpose() + .map_err(|err| { + let err_msg = format!("{err:?}"); + de::Error::custom(format_args!( + "Failed to deserialize PaymentsMandateReference `{}`", + err_msg + )) + })?; + + let payouts_data = value + .clone() + .map(|mandate_details| { + serde_json::from_value::>(mandate_details).map( + |optional_common_mandate_details| { + optional_common_mandate_details + .and_then(|common_mandate_details| common_mandate_details.payouts) + }, + ) + }) + .transpose() + .map_err(|err| { + let err_msg = format!("{err:?}"); + de::Error::custom(format_args!( + "Failed to deserialize CommonMandateReference `{}`", + err_msg + )) + })? + .flatten(); + + Ok(Some(CommonMandateReference { + payments: payments_data, + payouts: payouts_data, + })) +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -313,7 +398,12 @@ impl PaymentMethodCreate { payment_method_issuer_code: payment_method_migrate.payment_method_issuer_code, metadata: payment_method_migrate.metadata.clone(), payment_method_data: payment_method_migrate.payment_method_data.clone(), - connector_mandate_details: payment_method_migrate.connector_mandate_details.clone(), + connector_mandate_details: payment_method_migrate + .connector_mandate_details + .clone() + .map(|common_mandate_reference| { + PaymentsMandateReference::from(common_mandate_reference) + }), client_secret: None, billing: payment_method_migrate.billing.clone(), card: card_details, @@ -2328,7 +2418,11 @@ impl }), email: record.email, }), - connector_mandate_details, + connector_mandate_details: connector_mandate_details.map( + |payments_mandate_reference| { + CommonMandateReference::from(payments_mandate_reference) + }, + ), metadata: None, payment_method_issuer_code: None, card_network: None, diff --git a/crates/api_models/src/payouts.rs b/crates/api_models/src/payouts.rs index 237d165d572..4c8a73b75a8 100644 --- a/crates/api_models/src/payouts.rs +++ b/crates/api_models/src/payouts.rs @@ -184,6 +184,9 @@ pub struct PayoutCreateRequest { /// Customer's phone country code. _Deprecated: Use customer object instead._ #[schema(deprecated, max_length = 255, example = "+1")] pub phone_country_code: Option, + + /// Identifier for payout method + pub payout_method_id: Option, } impl PayoutCreateRequest { @@ -568,6 +571,9 @@ pub struct PayoutCreateResponse { #[remove_in(PayoutCreateResponse)] #[schema(value_type = Option, max_length = 1024, example = "Invalid card details")] pub unified_message: Option, + + /// Identifier for payout method + pub payout_method_id: Option, } /// The payout method information for response diff --git a/crates/diesel_models/src/payment_method.rs b/crates/diesel_models/src/payment_method.rs index 76613984363..e8c6e1737b3 100644 --- a/crates/diesel_models/src/payment_method.rs +++ b/crates/diesel_models/src/payment_method.rs @@ -1,8 +1,13 @@ use std::collections::HashMap; use common_enums::MerchantStorageScheme; -use common_utils::{encryption::Encryption, pii}; +use common_utils::{ + encryption::Encryption, + errors::{CustomResult, ParsingError}, + pii, +}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; +use error_stack::ResultExt; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -77,7 +82,7 @@ pub struct PaymentMethod { pub payment_method_data: Option, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -165,7 +170,7 @@ pub struct PaymentMethodNew { pub payment_method_data: Option, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -293,7 +298,7 @@ pub enum PaymentMethodUpdate { locker_fingerprint_id: Option, }, ConnectorMandateDetailsUpdate { - connector_mandate_details: Option, + connector_mandate_details: Option, }, } @@ -318,7 +323,7 @@ pub struct PaymentMethodUpdateInternal { status: Option, locker_id: Option, payment_method_type_v2: Option, - connector_mandate_details: Option, + connector_mandate_details: Option, updated_by: Option, payment_method_subtype: Option, last_modified: PrimitiveDateTime, @@ -970,3 +975,131 @@ impl std::ops::DerefMut for PaymentsMandateReference { } common_utils::impl_to_sql_from_sql_json!(PaymentsMandateReference); + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize)] +pub struct PayoutsMandateReferenceRecord { + pub transfer_method_id: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct PayoutsMandateReference( + pub HashMap, +); + +impl std::ops::Deref for PayoutsMandateReference { + type Target = + HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for PayoutsMandateReference { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct CommonMandateReference { + pub payments: Option, + pub payouts: Option, +} + +impl CommonMandateReference { + pub fn get_mandate_details_value(&self) -> CustomResult { + let mut payments = self + .payments + .as_ref() + .map_or_else(|| Ok(serde_json::json!({})), serde_json::to_value) + .change_context(ParsingError::StructParseFailure("payment mandate details"))?; + + self.payouts + .as_ref() + .map(|payouts_mandate| { + serde_json::to_value(payouts_mandate).map(|payouts_mandate_value| { + payments.as_object_mut().map(|payments_object| { + payments_object.insert("payouts".to_string(), payouts_mandate_value); + }) + }) + }) + .transpose() + .change_context(ParsingError::StructParseFailure("payout mandate details"))?; + + Ok(payments) + } +} + +impl diesel::serialize::ToSql for CommonMandateReference { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, diesel::pg::Pg>, + ) -> diesel::serialize::Result { + let payments = self.get_mandate_details_value()?; + + >::to_sql(&payments, &mut out.reborrow()) + } +} + +impl diesel::deserialize::FromSql + for CommonMandateReference +where + serde_json::Value: diesel::deserialize::FromSql, +{ + fn from_sql(bytes: DB::RawValue<'_>) -> diesel::deserialize::Result { + let value = >::from_sql(bytes)?; + + let payments_data = value + .clone() + .as_object_mut() + .map(|obj| { + obj.remove("payouts"); + + serde_json::from_value::(serde_json::Value::Object( + obj.clone(), + )) + .inspect_err(|err| { + router_env::logger::error!("Failed to parse payments data: {}", err); + }) + .change_context(ParsingError::StructParseFailure( + "Failed to parse payments data", + )) + }) + .transpose()?; + + let payouts_data = serde_json::from_value::>(value) + .inspect_err(|err| { + router_env::logger::error!("Failed to parse payouts data: {}", err); + }) + .change_context(ParsingError::StructParseFailure( + "Failed to parse payouts data", + )) + .map(|optional_common_mandate_details| { + optional_common_mandate_details + .and_then(|common_mandate_details| common_mandate_details.payouts) + })?; + + Ok(Self { + payments: payments_data, + payouts: payouts_data, + }) + } +} + +impl From for CommonMandateReference { + fn from(payment_reference: PaymentsMandateReference) -> Self { + Self { + payments: Some(payment_reference), + payouts: None, + } + } +} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 1180cb68f7c..c69641ebd11 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1348,6 +1348,26 @@ impl PhoneDetailsData for PhoneDetails { } } +#[cfg(feature = "payouts")] +pub trait PayoutFulfillRequestData { + fn get_connector_payout_id(&self) -> Result; + fn get_connector_transfer_method_id(&self) -> Result; +} +#[cfg(feature = "payouts")] +impl PayoutFulfillRequestData for hyperswitch_domain_models::router_request_types::PayoutsData { + fn get_connector_payout_id(&self) -> Result { + self.connector_payout_id + .clone() + .ok_or_else(missing_field_err("connector_payout_id")) + } + + fn get_connector_transfer_method_id(&self) -> Result { + self.connector_transfer_method_id + .clone() + .ok_or_else(missing_field_err("connector_transfer_method_id")) + } +} + pub trait CustomerData { fn get_email(&self) -> Result; } diff --git a/crates/hyperswitch_domain_models/src/mandates.rs b/crates/hyperswitch_domain_models/src/mandates.rs index d12b041a8ab..53104cc692f 100644 --- a/crates/hyperswitch_domain_models/src/mandates.rs +++ b/crates/hyperswitch_domain_models/src/mandates.rs @@ -1,10 +1,17 @@ +use std::collections::HashMap; + use api_models::payments::{ AcceptanceType as ApiAcceptanceType, CustomerAcceptance as ApiCustomerAcceptance, MandateAmountData as ApiMandateAmountData, MandateData as ApiMandateData, MandateType, OnlineMandate as ApiOnlineMandate, }; use common_enums::Currency; -use common_utils::{date_time, errors::ParsingError, pii, types::MinorUnit}; +use common_utils::{ + date_time, + errors::{CustomResult, ParsingError}, + pii, + types::MinorUnit, +}; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; use time::PrimitiveDateTime; @@ -254,3 +261,253 @@ impl MandateAmountData { self.metadata.clone() } } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentsMandateReferenceRecord { + pub connector_mandate_id: String, + pub payment_method_type: Option, + pub original_payment_authorized_amount: Option, + pub original_payment_authorized_currency: Option, + pub mandate_metadata: Option, + pub connector_mandate_status: Option, + pub connector_mandate_request_reference_id: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentsMandateReferenceRecord { + pub connector_mandate_id: String, + pub payment_method_subtype: Option, + pub original_payment_authorized_amount: Option, + pub original_payment_authorized_currency: Option, + pub mandate_metadata: Option, + pub connector_mandate_status: Option, + pub connector_mandate_request_reference_id: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PayoutsMandateReferenceRecord { + pub transfer_method_id: Option, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PayoutsMandateReference( + pub HashMap, +); + +impl std::ops::Deref for PayoutsMandateReference { + type Target = + HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for PayoutsMandateReference { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct PaymentsMandateReference( + pub HashMap, +); + +impl std::ops::Deref for PaymentsMandateReference { + type Target = + HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +impl std::ops::DerefMut for PaymentsMandateReference { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct CommonMandateReference { + pub payments: Option, + pub payouts: Option, +} + +impl CommonMandateReference { + pub fn get_mandate_details_value(&self) -> CustomResult { + let mut payments = self + .payments + .as_ref() + .map_or_else(|| Ok(serde_json::json!({})), serde_json::to_value) + .change_context(ParsingError::StructParseFailure("payment mandate details"))?; + + self.payouts + .as_ref() + .map(|payouts_mandate| { + serde_json::to_value(payouts_mandate).map(|payouts_mandate_value| { + payments.as_object_mut().map(|payments_object| { + payments_object.insert("payouts".to_string(), payouts_mandate_value); + }) + }) + }) + .transpose() + .change_context(ParsingError::StructParseFailure("payout mandate details"))?; + + Ok(payments) + } +} + +impl From for CommonMandateReference { + fn from(value: diesel_models::CommonMandateReference) -> Self { + Self { + payments: value.payments.map(|payments| payments.into()), + payouts: value.payouts.map(|payouts| payouts.into()), + } + } +} + +impl From for diesel_models::CommonMandateReference { + fn from(value: CommonMandateReference) -> Self { + Self { + payments: value.payments.map(|payments| payments.into()), + payouts: value.payouts.map(|payouts| payouts.into()), + } + } +} + +impl From for PayoutsMandateReference { + fn from(value: diesel_models::PayoutsMandateReference) -> Self { + Self( + value + .0 + .into_iter() + .map(|(key, record)| (key, record.into())) + .collect(), + ) + } +} + +impl From for diesel_models::PayoutsMandateReference { + fn from(value: PayoutsMandateReference) -> Self { + Self( + value + .0 + .into_iter() + .map(|(key, record)| (key, record.into())) + .collect(), + ) + } +} + +impl From for PaymentsMandateReference { + fn from(value: diesel_models::PaymentsMandateReference) -> Self { + Self( + value + .0 + .into_iter() + .map(|(key, record)| (key, record.into())) + .collect(), + ) + } +} + +impl From for diesel_models::PaymentsMandateReference { + fn from(value: PaymentsMandateReference) -> Self { + Self( + value + .0 + .into_iter() + .map(|(key, record)| (key, record.into())) + .collect(), + ) + } +} + +impl From for PayoutsMandateReferenceRecord { + fn from(value: diesel_models::PayoutsMandateReferenceRecord) -> Self { + Self { + transfer_method_id: value.transfer_method_id, + } + } +} + +impl From for diesel_models::PayoutsMandateReferenceRecord { + fn from(value: PayoutsMandateReferenceRecord) -> Self { + Self { + transfer_method_id: value.transfer_method_id, + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +impl From for PaymentsMandateReferenceRecord { + fn from(value: diesel_models::PaymentsMandateReferenceRecord) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_type: value.payment_method_type, + original_payment_authorized_amount: value.original_payment_authorized_amount, + original_payment_authorized_currency: value.original_payment_authorized_currency, + mandate_metadata: value.mandate_metadata, + connector_mandate_status: value.connector_mandate_status, + connector_mandate_request_reference_id: value.connector_mandate_request_reference_id, + } + } +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +impl From for diesel_models::PaymentsMandateReferenceRecord { + fn from(value: PaymentsMandateReferenceRecord) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_type: value.payment_method_type, + original_payment_authorized_amount: value.original_payment_authorized_amount, + original_payment_authorized_currency: value.original_payment_authorized_currency, + mandate_metadata: value.mandate_metadata, + connector_mandate_status: value.connector_mandate_status, + connector_mandate_request_reference_id: value.connector_mandate_request_reference_id, + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for PaymentsMandateReferenceRecord { + fn from(value: diesel_models::PaymentsMandateReferenceRecord) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_subtype: value.payment_method_subtype, + original_payment_authorized_amount: value.original_payment_authorized_amount, + original_payment_authorized_currency: value.original_payment_authorized_currency, + mandate_metadata: value.mandate_metadata, + connector_mandate_status: value.connector_mandate_status, + connector_mandate_request_reference_id: value.connector_mandate_request_reference_id, + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for diesel_models::PaymentsMandateReferenceRecord { + fn from(value: PaymentsMandateReferenceRecord) -> Self { + Self { + connector_mandate_id: value.connector_mandate_id, + payment_method_subtype: value.payment_method_subtype, + original_payment_authorized_amount: value.original_payment_authorized_amount, + original_payment_authorized_currency: value.original_payment_authorized_currency, + mandate_metadata: value.mandate_metadata, + connector_mandate_status: value.connector_mandate_status, + connector_mandate_request_reference_id: value.connector_mandate_request_reference_id, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index ed52325c231..cd85d56b72f 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -15,6 +15,7 @@ use serde_json::Value; use super::behaviour; use crate::{ + mandates::CommonMandateReference, router_data, type_encryption::{crypto_operation, CryptoOperation}, }; @@ -659,7 +660,7 @@ common_utils::create_list_wrapper!( pub fn is_merchant_connector_account_id_in_connector_mandate_details( &self, profile_id: Option<&id_type::ProfileId>, - connector_mandate_details: &diesel_models::PaymentsMandateReference, + connector_mandate_details: &CommonMandateReference, ) -> bool { let mca_ids = self .iter() @@ -671,8 +672,11 @@ common_utils::create_list_wrapper!( .collect::>(); connector_mandate_details - .keys() - .any(|mca_id| mca_ids.contains(mca_id)) + .payments + .as_ref() + .as_ref().is_some_and(|payments| { + payments.0.keys().any(|mca_id| mca_ids.contains(mca_id)) + }) } } ); diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index d03762dd64e..71ad9ee9275 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -2,7 +2,7 @@ use common_utils::crypto::Encryptable; use common_utils::{ crypto::OptionalEncryptableValue, - errors::{CustomResult, ValidationError}, + errors::{CustomResult, ParsingError, ValidationError}, pii, type_name, types::keymanager, }; @@ -13,7 +13,10 @@ use time::PrimitiveDateTime; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::type_encryption::OptionalEncryptableJsonType; -use crate::type_encryption::{crypto_operation, AsyncLift, CryptoOperation}; +use crate::{ + mandates::{CommonMandateReference, PaymentsMandateReference}, + type_encryption::{crypto_operation, AsyncLift, CryptoOperation}, +}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] @@ -84,7 +87,7 @@ pub struct PaymentMethod { OptionalEncryptableJsonType, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, - pub connector_mandate_details: Option, + pub connector_mandate_details: Option, pub customer_acceptance: Option, pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, @@ -138,6 +141,69 @@ impl PaymentMethod { pub fn get_payment_method_subtype(&self) -> Option { self.payment_method_subtype } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + pub fn get_common_mandate_reference(&self) -> Result { + let payments_data = self + .connector_mandate_details + .clone() + .map(|mut mandate_details| { + mandate_details + .as_object_mut() + .map(|obj| obj.remove("payouts")); + + serde_json::from_value::(mandate_details).inspect_err( + |err| { + router_env::logger::error!("Failed to parse payments data: {:?}", err); + }, + ) + }) + .transpose() + .map_err(|err| { + router_env::logger::error!("Failed to parse payments data: {:?}", err); + ParsingError::StructParseFailure("Failed to parse payments data") + })?; + + let payouts_data = self + .connector_mandate_details + .clone() + .map(|mandate_details| { + serde_json::from_value::>(mandate_details) + .inspect_err(|err| { + router_env::logger::error!("Failed to parse payouts data: {:?}", err); + }) + .map(|optional_common_mandate_details| { + optional_common_mandate_details + .and_then(|common_mandate_details| common_mandate_details.payouts) + }) + }) + .transpose() + .map_err(|err| { + router_env::logger::error!("Failed to parse payouts data: {:?}", err); + ParsingError::StructParseFailure("Failed to parse payouts data") + })? + .flatten(); + + Ok(CommonMandateReference { + payments: payments_data, + payouts: payouts_data, + }) + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + pub fn get_common_mandate_reference(&self) -> Result { + if let Some(value) = &self.connector_mandate_details { + Ok(value.clone()) + } else { + Ok(CommonMandateReference { + payments: None, + payouts: None, + }) + } + } } #[cfg(all( @@ -344,7 +410,7 @@ impl super::behaviour::Conversion for PaymentMethod { payment_method_data: self.payment_method_data.map(|val| val.into()), locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, - connector_mandate_details: self.connector_mandate_details, + connector_mandate_details: self.connector_mandate_details.map(|cmd| cmd.into()), customer_acceptance: self.customer_acceptance, status: self.status, network_transaction_id: self.network_transaction_id, @@ -397,7 +463,7 @@ impl super::behaviour::Conversion for PaymentMethod { .await?, locker_id: item.locker_id.map(VaultId::generate), last_used_at: item.last_used_at, - connector_mandate_details: item.connector_mandate_details, + connector_mandate_details: item.connector_mandate_details.map(|cmd| cmd.into()), customer_acceptance: item.customer_acceptance, status: item.status, network_transaction_id: item.network_transaction_id, @@ -455,7 +521,7 @@ impl super::behaviour::Conversion for PaymentMethod { payment_method_data: self.payment_method_data.map(|val| val.into()), locker_id: self.locker_id.map(|id| id.get_string_repr().clone()), last_used_at: self.last_used_at, - connector_mandate_details: self.connector_mandate_details, + connector_mandate_details: self.connector_mandate_details.map(|cmd| cmd.into()), customer_acceptance: self.customer_acceptance, status: self.status, network_transaction_id: self.network_transaction_id, @@ -474,3 +540,173 @@ impl super::behaviour::Conversion for PaymentMethod { }) } } + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[cfg(test)] +mod tests { + #![allow(clippy::unwrap_used)] + use common_utils::id_type::MerchantConnectorAccountId; + + use super::*; + + fn get_payment_method_with_mandate_data( + mandate_data: Option, + ) -> PaymentMethod { + let payment_method = PaymentMethod { + customer_id: common_utils::id_type::CustomerId::default(), + merchant_id: common_utils::id_type::MerchantId::default(), + payment_method_id: String::from("abc"), + accepted_currency: None, + scheme: None, + token: None, + cardholder_name: None, + issuer_name: None, + issuer_country: None, + payer_country: None, + is_stored: None, + swift_code: None, + direct_debit_token: None, + created_at: common_utils::date_time::now(), + last_modified: common_utils::date_time::now(), + payment_method: None, + payment_method_type: None, + payment_method_issuer: None, + payment_method_issuer_code: None, + metadata: None, + payment_method_data: None, + locker_id: None, + last_used_at: common_utils::date_time::now(), + connector_mandate_details: mandate_data, + customer_acceptance: None, + status: storage_enums::PaymentMethodStatus::Active, + network_transaction_id: None, + client_secret: None, + payment_method_billing_address: None, + updated_by: None, + version: common_enums::ApiVersion::V1, + network_token_requestor_reference_id: None, + network_token_locker_id: None, + network_token_payment_method_data: None, + }; + payment_method.clone() + } + + #[test] + fn test_get_common_mandate_reference_payments_only() { + let connector_mandate_details = serde_json::json!({ + "mca_kGz30G8B95MxRwmeQqy6": { + "mandate_metadata": null, + "payment_method_type": null, + "connector_mandate_id": "RcBww0a02c-R22w22w22wNJV-V14o20u24y18sTB18sB24y06g04eVZ04e20u14o", + "connector_mandate_status": "active", + "original_payment_authorized_amount": 51, + "original_payment_authorized_currency": "USD", + "connector_mandate_request_reference_id": "RowbU9ULN9H59bMhWk" + } + }); + + let payment_method = get_payment_method_with_mandate_data(Some(connector_mandate_details)); + + let result = payment_method.get_common_mandate_reference(); + + assert!(result.is_ok()); + let common_mandate = result.unwrap(); + + assert!(common_mandate.payments.is_some()); + assert!(common_mandate.payouts.is_none()); + + let payments = common_mandate.payments.unwrap(); + let result_mca = MerchantConnectorAccountId::wrap("mca_kGz30G8B95MxRwmeQqy6".to_string()); + assert!( + result_mca.is_ok(), + "Expected Ok, but got Err: {:?}", + result_mca + ); + let mca = result_mca.unwrap(); + assert!(payments.0.contains_key(&mca)); + } + + #[test] + fn test_get_common_mandate_reference_empty_details() { + let payment_method = get_payment_method_with_mandate_data(None); + let result = payment_method.get_common_mandate_reference(); + + assert!(result.is_ok()); + let common_mandate = result.unwrap(); + + assert!(common_mandate.payments.is_none()); + assert!(common_mandate.payouts.is_none()); + } + + #[test] + fn test_get_common_mandate_reference_payouts_only() { + let connector_mandate_details = serde_json::json!({ + "payouts": { + "mca_DAHVXbXpbYSjnL7fQWEs": { + "transfer_method_id": "TRM-678ab3997b16cb7cd" + } + } + }); + + let payment_method = get_payment_method_with_mandate_data(Some(connector_mandate_details)); + + let result = payment_method.get_common_mandate_reference(); + + assert!(result.is_ok()); + let common_mandate = result.unwrap(); + + assert!(common_mandate.payments.is_some()); + assert!(common_mandate.payouts.is_some()); + + let payouts = common_mandate.payouts.unwrap(); + let result_mca = MerchantConnectorAccountId::wrap("mca_DAHVXbXpbYSjnL7fQWEs".to_string()); + assert!( + result_mca.is_ok(), + "Expected Ok, but got Err: {:?}", + result_mca + ); + let mca = result_mca.unwrap(); + assert!(payouts.0.contains_key(&mca)); + } + + #[test] + fn test_get_common_mandate_reference_invalid_data() { + let connector_mandate_details = serde_json::json!("invalid"); + let payment_method = get_payment_method_with_mandate_data(Some(connector_mandate_details)); + let result = payment_method.get_common_mandate_reference(); + assert!(result.is_err()); + } + + #[test] + fn test_get_common_mandate_reference_with_payments_and_payouts_details() { + let connector_mandate_details = serde_json::json!({ + "mca_kGz30G8B95MxRwmeQqy6": { + "mandate_metadata": null, + "payment_method_type": null, + "connector_mandate_id": "RcBww0a02c-R22w22w22wNJV-V14o20u24y18sTB18sB24y06g04eVZ04e20u14o", + "connector_mandate_status": "active", + "original_payment_authorized_amount": 51, + "original_payment_authorized_currency": "USD", + "connector_mandate_request_reference_id": "RowbU9ULN9H59bMhWk" + }, + "payouts": { + "mca_DAHVXbXpbYSjnL7fQWEs": { + "transfer_method_id": "TRM-678ab3997b16cb7cd" + } + } + }); + + let payment_method = get_payment_method_with_mandate_data(Some(connector_mandate_details)); + + let result = payment_method.get_common_mandate_reference(); + + assert!(result.is_ok()); + let common_mandate = result.unwrap(); + + assert!(common_mandate.payments.is_some()); + assert!(common_mandate.payouts.is_some()); + } +} diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 4598fa5d1b5..51e4cd5c8ec 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -823,6 +823,7 @@ pub struct PayoutsData { // New minor amount for amount framework pub minor_amount: MinorUnit, pub priority: Option, + pub connector_transfer_method_id: Option, } #[derive(Debug, Default, Clone)] diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 25ece15d47d..d9f0db4c2ac 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -41,6 +41,12 @@ use diesel_models::{ use error_stack::{report, ResultExt}; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData}; +#[cfg(all( + feature = "v2", + feature = "payment_methods_v2", + feature = "customer_v2" +))] +use hyperswitch_domain_models::mandates::CommonMandateReference; use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use masking::ExposeInterface; @@ -1340,7 +1346,7 @@ pub async fn create_payment_method_in_db( api::payment_methods::PaymentMethodsData, >, key_store: &domain::MerchantKeyStore, - connector_mandate_details: Option, + connector_mandate_details: Option, status: Option, network_transaction_id: Option, storage_scheme: enums::MerchantStorageScheme, diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 7be645a81c7..1f4fb6904fd 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -47,6 +47,7 @@ use euclid::frontend::dir; use hyperswitch_constraint_graph as cgraph; #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] use hyperswitch_domain_models::customer::CustomerUpdate; +use hyperswitch_domain_models::mandates::CommonMandateReference; #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -845,9 +846,10 @@ pub async fn skip_locker_call_and_migrate_payment_method( .clone() .and_then(|val| if val == json!({}) { None } else { Some(true) }) .or_else(|| { - req.connector_mandate_details - .clone() - .and_then(|val| (!val.0.is_empty()).then_some(false)) + req.connector_mandate_details.clone().and_then(|val| { + val.payments + .and_then(|payin_val| (!payin_val.0.is_empty()).then_some(false)) + }) }), ); @@ -2816,11 +2818,11 @@ pub async fn update_payment_method_connector_mandate_details( key_store: &domain::MerchantKeyStore, db: &dyn db::StorageInterface, pm: domain::PaymentMethod, - connector_mandate_details: Option, + connector_mandate_details: Option, storage_scheme: MerchantStorageScheme, ) -> errors::CustomResult<(), errors::VaultError> { let pm_update = payment_method::PaymentMethodUpdate::ConnectorMandateDetailsUpdate { - connector_mandate_details, + connector_mandate_details: connector_mandate_details.map(|cmd| cmd.into()), }; db.update_payment_method(&(state.into()), key_store, pm, pm_update, storage_scheme) @@ -2838,11 +2840,20 @@ pub async fn update_payment_method_connector_mandate_details( key_store: &domain::MerchantKeyStore, db: &dyn db::StorageInterface, pm: domain::PaymentMethod, - connector_mandate_details: Option, + connector_mandate_details: Option, storage_scheme: MerchantStorageScheme, ) -> errors::CustomResult<(), errors::VaultError> { + let connector_mandate_details_value = connector_mandate_details + .map(|common_mandate| { + common_mandate.get_mandate_details_value().map_err(|err| { + router_env::logger::error!("Failed to get get_mandate_details_value : {:?}", err); + errors::VaultError::UpdateInPaymentMethodDataTableFailed + }) + }) + .transpose()?; + let pm_update = payment_method::PaymentMethodUpdate::ConnectorMandateDetailsUpdate { - connector_mandate_details, + connector_mandate_details: connector_mandate_details_value, }; db.update_payment_method(&(state.into()), key_store, pm, pm_update, storage_scheme) @@ -4935,14 +4946,7 @@ pub async fn list_customer_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("unable to decrypt payment method billing address details")?; let connector_mandate_details = pm - .connector_mandate_details - .clone() - .map(|val| { - val.parse_value::( - "PaymentsMandateReference", - ) - }) - .transpose() + .get_common_mandate_reference() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to deserialize to Payment Mandate Reference ")?; let mca_enabled = get_mca_status( @@ -4951,7 +4955,7 @@ pub async fn list_customer_payment_method( profile_id.clone(), merchant_account.get_id(), is_connector_agnostic_mit_enabled, - connector_mandate_details, + Some(connector_mandate_details), pm.network_transaction_id.as_ref(), ) .await?; @@ -5062,7 +5066,7 @@ pub async fn list_customer_payment_method( not(feature = "payment_methods_v2"), not(feature = "customer_v2") ))] -async fn get_pm_list_context( +pub async fn get_pm_list_context( state: &routes::SessionState, payment_method: &enums::PaymentMethod, #[cfg(feature = "payouts")] key_store: &domain::MerchantKeyStore, @@ -5217,7 +5221,7 @@ pub async fn get_mca_status( profile_id: Option, merchant_id: &id_type::MerchantId, is_connector_agnostic_mit_enabled: bool, - connector_mandate_details: Option, + connector_mandate_details: Option, network_transaction_id: Option<&String>, ) -> errors::RouterResult { if is_connector_agnostic_mit_enabled && network_transaction_id.is_some() { @@ -5255,7 +5259,7 @@ pub async fn get_mca_status( profile_id: Option, merchant_id: &id_type::MerchantId, is_connector_agnostic_mit_enabled: bool, - connector_mandate_details: Option<&payment_method::PaymentsMandateReference>, + connector_mandate_details: Option<&CommonMandateReference>, network_transaction_id: Option<&String>, merchant_connector_accounts: &domain::MerchantConnectorAccounts, ) -> bool { diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 5b8988dace0..8e31ab7975f 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5945,16 +5945,12 @@ pub async fn decide_connector_for_normal_or_recurring_payment( where D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, { - let connector_mandate_details = &payment_method_info - .connector_mandate_details - .clone() - .map(|details| { - details - .parse_value::("connector_mandate_details") - }) - .transpose() + let connector_common_mandate_details = payment_method_info + .get_common_mandate_reference() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to deserialize connector mandate details")?; + .attach_printable("Failed to get the common mandate reference")?; + + let connector_mandate_details = connector_common_mandate_details.payments; let mut connector_choice = None; diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 56667b0517f..09fb3714421 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6805,7 +6805,7 @@ where pub async fn validate_merchant_connector_ids_in_connector_mandate_details( state: &SessionState, key_store: &domain::MerchantKeyStore, - connector_mandate_details: &api_models::payment_methods::PaymentsMandateReference, + connector_mandate_details: &api_models::payment_methods::CommonMandateReference, merchant_id: &id_type::MerchantId, card_network: Option, ) -> CustomResult<(), errors::ApiErrorResponse> { @@ -6833,46 +6833,49 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details( }) .collect(); - for (migrating_merchant_connector_id, migrating_connector_mandate_details) in - connector_mandate_details.0.clone() - { - match ( - card_network.clone(), - merchant_connector_account_details_hash_map.get(&migrating_merchant_connector_id), - ) { - (Some(enums::CardNetwork::Discover), Some(merchant_connector_account_details)) => { - if let ("cybersource", None) = ( - merchant_connector_account_details.connector_name.as_str(), - migrating_connector_mandate_details - .original_payment_authorized_amount - .zip( - migrating_connector_mandate_details - .original_payment_authorized_currency, - ), - ) { - Err(errors::ApiErrorResponse::MissingRequiredFields { - field_names: vec![ - "original_payment_authorized_currency", - "original_payment_authorized_amount", - ], - }) - .attach_printable(format!( - "Invalid connector_mandate_details provided for connector {:?}", - migrating_merchant_connector_id - ))? + if let Some(payment_mandate_reference) = &connector_mandate_details.payments { + let payments_map = payment_mandate_reference.0.clone(); + for (migrating_merchant_connector_id, migrating_connector_mandate_details) in payments_map { + match ( + card_network.clone(), + merchant_connector_account_details_hash_map.get(&migrating_merchant_connector_id), + ) { + (Some(enums::CardNetwork::Discover), Some(merchant_connector_account_details)) => { + if let ("cybersource", None) = ( + merchant_connector_account_details.connector_name.as_str(), + migrating_connector_mandate_details + .original_payment_authorized_amount + .zip( + migrating_connector_mandate_details + .original_payment_authorized_currency, + ), + ) { + Err(errors::ApiErrorResponse::MissingRequiredFields { + field_names: vec![ + "original_payment_authorized_currency", + "original_payment_authorized_amount", + ], + }) + .attach_printable(format!( + "Invalid connector_mandate_details provided for connector {:?}", + migrating_merchant_connector_id + ))? + } } + (_, Some(_)) => (), + (_, None) => Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "merchant_connector_id", + }) + .attach_printable_lazy(|| { + format!( + "{:?} invalid merchant connector id in connector_mandate_details", + migrating_merchant_connector_id + ) + })?, } - (_, Some(_)) => (), - (_, None) => Err(errors::ApiErrorResponse::InvalidDataValue { - field_name: "merchant_connector_id", - }) - .attach_printable_lazy(|| { - format!( - "{:?} invalid merchant connector id in connector_mandate_details", - migrating_merchant_connector_id - ) - })?, } + } else { + router_env::logger::error!("payment mandate reference not found"); } Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 9692b1ca8a5..07342ffb4e6 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -6,7 +6,7 @@ use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; use common_enums::{AuthorizationStatus, SessionUpdateStatus}; use common_utils::{ - ext_traits::{AsyncExt, Encode, ValueExt}, + ext_traits::{AsyncExt, Encode}, types::{keymanager::KeyManagerState, ConnectorTransactionId, MinorUnit}, }; use error_stack::{report, ResultExt}; @@ -1591,14 +1591,7 @@ async fn payment_response_update_tracker( { // Parse value to check for mandates' existence let mandate_details = payment_method - .connector_mandate_details - .clone() - .map(|val| { - val.parse_value::( - "PaymentsMandateReference", - ) - }) - .transpose() + .get_common_mandate_reference() .change_context( errors::ApiErrorResponse::InternalServerError, ) @@ -1610,15 +1603,11 @@ async fn payment_response_update_tracker( payment_data.payment_attempt.merchant_connector_id.clone() { // check if the mandate has not already been set to active - if !mandate_details - .as_ref() - .map(|payment_mandate_reference| { - - payment_mandate_reference.0.get(&mca_id) + if !mandate_details.payments + .as_ref() + .and_then(|payments| payments.0.get(&mca_id)) .map(|payment_mandate_reference_record| payment_mandate_reference_record.connector_mandate_status == Some(common_enums::ConnectorMandateStatus::Active)) .unwrap_or(false) - }) - .unwrap_or(false) { let (connector_mandate_id, mandate_metadata,connector_mandate_request_reference_id) = payment_data.payment_attempt.connector_mandate_detail.clone() @@ -1627,7 +1616,7 @@ async fn payment_response_update_tracker( // Update the connector mandate details with the payment attempt connector mandate id let connector_mandate_details = tokenization::update_connector_mandate_details( - mandate_details, + Some(mandate_details), payment_data.payment_attempt.payment_method_type, Some( payment_data diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 271a5679282..dd592bc3064 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -154,7 +154,7 @@ pub fn make_dsl_input_for_payouts( .map(api_enums::PaymentMethod::foreign_from), payment_method_type: payout_data .payout_method_data - .clone() + .as_ref() .map(api_enums::PaymentMethodType::foreign_from), card_network: None, }; diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index ea81b8cb16f..597831231a3 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -15,6 +15,9 @@ use common_utils::{ id_type, pii, }; use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::mandates::{ + CommonMandateReference, PaymentsMandateReference, PaymentsMandateReferenceRecord, +}; use masking::{ExposeInterface, Secret}; use router_env::{instrument, tracing}; @@ -469,7 +472,7 @@ where .connector_mandate_details .clone() .map(|val| { - val.parse_value::( + val.parse_value::( "PaymentsMandateReference", ) }) @@ -1213,7 +1216,7 @@ pub fn add_connector_mandate_details_in_payment_method( connector_mandate_id: Option, mandate_metadata: Option>, connector_mandate_request_reference_id: Option, -) -> Option { +) -> Option { let mut mandate_details = HashMap::new(); if let Some((mca_id, connector_mandate_id)) = @@ -1221,7 +1224,7 @@ pub fn add_connector_mandate_details_in_payment_method( { mandate_details.insert( mca_id, - diesel_models::PaymentsMandateReferenceRecord { + PaymentsMandateReferenceRecord { connector_mandate_id, payment_method_type, original_payment_authorized_amount: authorized_amount, @@ -1231,7 +1234,10 @@ pub fn add_connector_mandate_details_in_payment_method( connector_mandate_request_reference_id, }, ); - Some(diesel_models::PaymentsMandateReference(mandate_details)) + Some(CommonMandateReference { + payments: Some(PaymentsMandateReference(mandate_details)), + payouts: None, + }) } else { None } @@ -1240,7 +1246,7 @@ pub fn add_connector_mandate_details_in_payment_method( #[allow(clippy::too_many_arguments)] #[cfg(feature = "v1")] pub fn update_connector_mandate_details( - mandate_details: Option, + mandate_details: Option, payment_method_type: Option, authorized_amount: Option, authorized_currency: Option, @@ -1248,13 +1254,16 @@ pub fn update_connector_mandate_details( connector_mandate_id: Option, mandate_metadata: Option>, connector_mandate_request_reference_id: Option, -) -> RouterResult> { - let mandate_reference = match mandate_details { +) -> RouterResult> { + let mandate_reference = match mandate_details + .as_ref() + .and_then(|common_mandate| common_mandate.payments.clone()) + { Some(mut payment_mandate_reference) => { if let Some((mca_id, connector_mandate_id)) = merchant_connector_id.clone().zip(connector_mandate_id) { - let updated_record = diesel_models::PaymentsMandateReferenceRecord { + let updated_record = PaymentsMandateReferenceRecord { connector_mandate_id: connector_mandate_id.clone(), payment_method_type, original_payment_authorized_amount: authorized_amount, @@ -1268,7 +1277,7 @@ pub fn update_connector_mandate_details( payment_mandate_reference .entry(mca_id) .and_modify(|pm| *pm = updated_record) - .or_insert(diesel_models::PaymentsMandateReferenceRecord { + .or_insert(PaymentsMandateReferenceRecord { connector_mandate_id, payment_method_type, original_payment_authorized_amount: authorized_amount, @@ -1277,7 +1286,13 @@ pub fn update_connector_mandate_details( connector_mandate_status: Some(ConnectorMandateStatus::Active), connector_mandate_request_reference_id, }); - Some(payment_mandate_reference) + + let payout_data = mandate_details.and_then(|common_mandate| common_mandate.payouts); + + Some(CommonMandateReference { + payments: Some(payment_mandate_reference), + payouts: payout_data, + }) } else { None } @@ -1292,26 +1307,20 @@ pub fn update_connector_mandate_details( connector_mandate_request_reference_id, ), }; - let connector_mandate_details = mandate_reference - .map(|mand| mand.encode_to_value()) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to serialize customer acceptance to value")?; - - Ok(connector_mandate_details) + Ok(mandate_reference) } #[cfg(feature = "v1")] pub fn update_connector_mandate_details_status( merchant_connector_id: id_type::MerchantConnectorAccountId, - mut payment_mandate_reference: diesel_models::PaymentsMandateReference, + mut payment_mandate_reference: PaymentsMandateReference, status: ConnectorMandateStatus, -) -> RouterResult> { +) -> RouterResult> { let mandate_reference = { payment_mandate_reference .entry(merchant_connector_id) .and_modify(|pm| { - let update_rec = diesel_models::PaymentsMandateReferenceRecord { + let update_rec = PaymentsMandateReferenceRecord { connector_mandate_id: pm.connector_mandate_id.clone(), payment_method_type: pm.payment_method_type, original_payment_authorized_amount: pm.original_payment_authorized_amount, @@ -1326,11 +1335,9 @@ pub fn update_connector_mandate_details_status( }); Some(payment_mandate_reference) }; - let connector_mandate_details = mandate_reference - .map(|mandate| mandate.encode_to_value()) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to serialize customer acceptance to value")?; - Ok(connector_mandate_details) + Ok(Some(CommonMandateReference { + payments: mandate_reference, + payouts: None, + })) } diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index 11be2ebfb5b..58d5e22b4b9 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -4,7 +4,10 @@ pub mod helpers; pub mod retry; pub mod transformers; pub mod validator; -use std::{collections::HashSet, vec::IntoIter}; +use std::{ + collections::{HashMap, HashSet}, + vec::IntoIter, +}; #[cfg(feature = "olap")] use api_models::payments as payment_enums; @@ -21,10 +24,16 @@ use common_utils::{ use diesel_models::{ enums as storage_enums, generic_link::{GenericLinkNew, PayoutLink}, + CommonMandateReference, PayoutsMandateReference, PayoutsMandateReferenceRecord, +}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use diesel_models::{ + PaymentsMandateReference, PaymentsMandateReferenceRecord as PaymentsMandateReferenceRecordV2, }; use error_stack::{report, ResultExt}; #[cfg(feature = "olap")] use futures::future::join_all; +use hyperswitch_domain_models::payment_methods::PaymentMethod; use masking::{PeekInterface, Secret}; #[cfg(feature = "payout_retry")] use retry::GsmValidation; @@ -72,6 +81,7 @@ pub struct PayoutData { pub should_terminate: bool, pub payout_link: Option, pub current_locale: String, + pub payment_method: Option, } // ********************************************** CORE FLOWS ********************************************** @@ -319,7 +329,7 @@ pub async fn payouts_create_core( req: payouts::PayoutCreateRequest, ) -> RouterResponse { // Validate create request - let (payout_id, payout_method_data, profile_id, customer) = + let (payout_id, payout_method_data, profile_id, customer, payment_method) = validator::validate_create_request(&state, &merchant_account, &req, &key_store).await?; // Create DB entries @@ -333,6 +343,7 @@ pub async fn payouts_create_core( payout_method_data.as_ref(), &state.locale, customer.as_ref(), + payment_method.clone(), ) .await?; @@ -1126,12 +1137,13 @@ pub async fn call_connector_payout( ) .await?; // Create customer's disbursement account flow - complete_create_recipient_disburse_account( + Box::pin(complete_create_recipient_disburse_account( state, merchant_account, connector_data, payout_data, - ) + key_store, + )) .await?; // Payout creation flow Box::pin(complete_create_payout( @@ -1347,6 +1359,39 @@ pub async fn create_recipient( // Helps callee functions skip the execution payout_data.should_terminate = true; + } else if let Some(status) = recipient_create_data.status { + let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate { + connector_payout_id: payout_data + .payout_attempt + .connector_payout_id + .to_owned(), + status, + error_code: None, + error_message: None, + is_eligible: recipient_create_data.payout_eligible, + unified_code: None, + unified_message: None, + }; + payout_data.payout_attempt = db + .update_payout_attempt( + &payout_data.payout_attempt, + updated_payout_attempt, + &payout_data.payouts, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error updating payout_attempt in db")?; + payout_data.payouts = db + .update_payout( + &payout_data.payouts, + storage::PayoutsUpdate::StatusUpdate { status }, + &payout_data.payout_attempt, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error updating payouts in db")?; } } Err(err) => Err(errors::ApiErrorResponse::PayoutFailed { @@ -1958,17 +2003,29 @@ pub async fn complete_create_recipient_disburse_account( merchant_account: &domain::MerchantAccount, connector_data: &api::ConnectorData, payout_data: &mut PayoutData, + key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { if !payout_data.should_terminate - && payout_data.payout_attempt.status - == storage_enums::PayoutStatus::RequiresVendorAccountCreation + && matches!( + payout_data.payout_attempt.status, + storage_enums::PayoutStatus::RequiresVendorAccountCreation + | storage_enums::PayoutStatus::RequiresCreation + ) && connector_data .connector_name .supports_vendor_disburse_account_create_for_payout() + && helpers::should_create_connector_transfer_method(&*payout_data, connector_data)? + .is_none() { - create_recipient_disburse_account(state, merchant_account, connector_data, payout_data) - .await - .attach_printable("Creation of customer failed")?; + Box::pin(create_recipient_disburse_account( + state, + merchant_account, + connector_data, + payout_data, + key_store, + )) + .await + .attach_printable("Creation of customer failed")?; } Ok(()) } @@ -1978,6 +2035,7 @@ pub async fn create_recipient_disburse_account( merchant_account: &domain::MerchantAccount, connector_data: &api::ConnectorData, payout_data: &mut PayoutData, + key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { // 1. Form Router data let router_data = core_utils::construct_payout_router_data( @@ -2015,7 +2073,7 @@ pub async fn create_recipient_disburse_account( .status .unwrap_or(payout_attempt.status.to_owned()); let updated_payout_attempt = storage::PayoutAttemptUpdate::StatusUpdate { - connector_payout_id: payout_response_data.connector_payout_id, + connector_payout_id: payout_response_data.connector_payout_id.clone(), status, error_code: None, error_message: None, @@ -2033,6 +2091,89 @@ pub async fn create_recipient_disburse_account( .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error updating payout_attempt in db")?; + + if let ( + true, + Some(ref payout_method_data), + Some(connector_payout_id), + Some(customer_details), + Some(merchant_connector_id), + ) = ( + payout_data.payouts.recurring, + payout_data.payout_method_data.clone(), + payout_response_data.connector_payout_id.clone(), + payout_data.customer_details.clone(), + connector_data.merchant_connector_id.clone(), + ) { + let connector_mandate_details = HashMap::from([( + merchant_connector_id.clone(), + PayoutsMandateReferenceRecord { + transfer_method_id: Some(connector_payout_id), + }, + )]); + + let common_connector_mandate = CommonMandateReference { + payments: None, + payouts: Some(PayoutsMandateReference(connector_mandate_details)), + }; + + let connector_mandate_details_value = common_connector_mandate + .get_mandate_details_value() + .map_err(|err| { + router_env::logger::error!( + "Failed to get get_mandate_details_value : {:?}", + err + ); + errors::ApiErrorResponse::MandateUpdateFailed + })?; + + if let Some(pm_method) = payout_data.payment_method.clone() { + let pm_update = + diesel_models::PaymentMethodUpdate::ConnectorMandateDetailsUpdate { + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + connector_mandate_details: Some(connector_mandate_details_value), + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + connector_mandate_details: Some(common_connector_mandate), + }; + + payout_data.payment_method = Some( + db.update_payment_method( + &(state.into()), + key_store, + pm_method, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) + .attach_printable("Unable to find payment method")?, + ); + } else { + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] + let customer_id = Some(customer_details.customer_id); + + #[cfg(all(feature = "v2", feature = "customer_v2"))] + let customer_id = customer_details.merchant_reference_id; + + if let Some(customer_id) = customer_id { + helpers::save_payout_data_to_locker( + state, + payout_data, + &customer_id, + payout_method_data, + Some(connector_mandate_details_value), + merchant_account, + key_store, + ) + .await + .attach_printable("Failed to save payout data to locker")?; + } + }; + } } Err(err) => { let (error_code, error_message) = (Some(err.code), Some(err.message)); @@ -2311,6 +2452,7 @@ pub async fn fulfill_payout( payout_data, &customer_id, &payout_method_data, + None, merchant_account, key_store, ) @@ -2385,6 +2527,22 @@ pub async fn response_handler( ) -> RouterResponse { let payout_attempt = payout_data.payout_attempt.to_owned(); let payouts = payout_data.payouts.to_owned(); + + let payout_method_id: Option = payout_data.payment_method.as_ref().map(|pm| { + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + { + pm.payment_method_id.clone() + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + { + pm.id.clone().get_string_repr().to_string() + } + }); + let payout_link = payout_data.payout_link.to_owned(); let billing_address = payout_data.billing_address.to_owned(); let customer_details = payout_data.customer_details.to_owned(); @@ -2456,6 +2614,7 @@ pub async fn response_handler( .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to parse payout link's URL")?, + payout_method_id, }; Ok(services::ApplicationResponse::Json(response)) } @@ -2472,6 +2631,7 @@ pub async fn payout_create_db_entries( _stored_payout_method_data: Option<&payouts::PayoutMethodData>, _locale: &str, _customer: Option<&domain::Customer>, + _payment_method: Option, ) -> RouterResult { todo!() } @@ -2489,6 +2649,7 @@ pub async fn payout_create_db_entries( stored_payout_method_data: Option<&payouts::PayoutMethodData>, locale: &str, customer: Option<&domain::Customer>, + payment_method: Option, ) -> RouterResult { let db = &*state.store; let merchant_id = merchant_account.get_id(); @@ -2541,13 +2702,22 @@ pub async fn payout_create_db_entries( // Make payouts entry let currency = req.currency.to_owned().get_required_value("currency")?; - let payout_type = req.payout_type.to_owned(); - let payout_method_id = if stored_payout_method_data.is_some() { - req.payout_token.to_owned() - } else { - None + let (payout_method_id, payout_type) = match stored_payout_method_data { + Some(payout_method_data) => ( + payment_method + .as_ref() + .map(|pm| pm.payment_method_id.clone()), + Some(api_enums::PayoutType::foreign_from(payout_method_data)), + ), + None => ( + payment_method + .as_ref() + .map(|pm| pm.payment_method_id.clone()), + req.payout_type.to_owned(), + ), }; + let client_secret = utils::generate_id( consts::ID_LENGTH, format!("payout_{payout_id}_secret").as_str(), @@ -2666,6 +2836,7 @@ pub async fn payout_create_db_entries( profile_id: profile_id.to_owned(), payout_link, current_locale: locale.to_string(), + payment_method, }) } @@ -2855,6 +3026,23 @@ pub async fn make_payout_data( .await .transpose()?; + let payout_method_id = payouts.payout_method_id.clone(); + let mut payment_method: Option = None; + + if let Some(pm_id) = payout_method_id { + payment_method = Some( + db.find_payment_method( + &(state.into()), + key_store, + &pm_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) + .attach_printable("Unable to find payment method")?, + ); + } + Ok(PayoutData { billing_address, business_profile, @@ -2867,6 +3055,7 @@ pub async fn make_payout_data( profile_id, payout_link, current_locale: locale.to_string(), + payment_method, }) } diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 5ebdea0de67..77cd9c266ff 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -194,6 +194,36 @@ pub async fn make_payout_method_data( } } +pub fn should_create_connector_transfer_method( + payout_data: &PayoutData, + connector_data: &api::ConnectorData, +) -> RouterResult> { + let connector_transfer_method_id = payout_data.payment_method.as_ref().and_then(|pm| { + let common_mandate_reference = pm + .get_common_mandate_reference() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize connector mandate details") + .ok()?; + + connector_data + .merchant_connector_id + .as_ref() + .and_then(|merchant_connector_id| { + common_mandate_reference + .payouts + .and_then(|payouts_mandate_reference| { + payouts_mandate_reference + .get(merchant_connector_id) + .and_then(|payouts_mandate_reference_record| { + payouts_mandate_reference_record.transfer_method_id.clone() + }) + }) + }) + }); + + Ok(connector_transfer_method_id) +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") @@ -203,6 +233,7 @@ pub async fn save_payout_data_to_locker( payout_data: &mut PayoutData, customer_id: &id_type::CustomerId, payout_method_data: &api::PayoutMethodData, + connector_mandate_details: Option, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { @@ -290,14 +321,14 @@ pub async fn save_payout_data_to_locker( None, Some(bank.to_owned()), None, - api_enums::PaymentMethodType::foreign_from(bank.to_owned()), + api_enums::PaymentMethodType::foreign_from(bank), ), payouts::PayoutMethodData::Wallet(wallet) => ( payload, None, None, Some(wallet.to_owned()), - api_enums::PaymentMethodType::foreign_from(wallet.to_owned()), + api_enums::PaymentMethodType::foreign_from(wallet), ), payouts::PayoutMethodData::Card(_) => { Err(errors::ApiErrorResponse::InternalServerError)? @@ -409,9 +440,7 @@ pub async fn save_payout_data_to_locker( let card_isin = card_details.as_ref().map(|c| c.card_number.get_card_isin()); let mut payment_method = api::PaymentMethodCreate { - payment_method: Some(api_enums::PaymentMethod::foreign_from( - payout_method_data.to_owned(), - )), + payment_method: Some(api_enums::PaymentMethod::foreign_from(payout_method_data)), payment_method_type: Some(payment_method_type), payment_method_issuer: None, payment_method_issuer_code: None, @@ -497,7 +526,7 @@ pub async fn save_payout_data_to_locker( None, api::PaymentMethodCreate { payment_method: Some(api_enums::PaymentMethod::foreign_from( - payout_method_data.to_owned(), + payout_method_data, )), payment_method_type: Some(payment_method_type), payment_method_issuer: None, @@ -520,28 +549,30 @@ pub async fn save_payout_data_to_locker( // Insert new entry in payment_methods table if should_insert_in_pm_table { let payment_method_id = common_utils::generate_id(consts::ID_LENGTH, "pm"); - cards::create_payment_method( - state, - &new_payment_method, - customer_id, - &payment_method_id, - Some(stored_resp.card_reference.clone()), - merchant_account.get_id(), - None, - None, - card_details_encrypted.clone().map(Into::into), - key_store, - None, - None, - None, - merchant_account.storage_scheme, - None, - None, - None, - None, - None, - ) - .await?; + payout_data.payment_method = Some( + cards::create_payment_method( + state, + &new_payment_method, + customer_id, + &payment_method_id, + Some(stored_resp.card_reference.clone()), + merchant_account.get_id(), + None, + None, + card_details_encrypted.clone().map(Into::into), + key_store, + connector_mandate_details, + None, + None, + merchant_account.storage_scheme, + None, + None, + None, + None, + None, + ) + .await?, + ); } /* 1. Delete from locker @@ -600,22 +631,28 @@ pub async fn save_payout_data_to_locker( let pm_update = storage::PaymentMethodUpdate::PaymentMethodDataUpdate { payment_method_data: card_details_encrypted.map(Into::into), }; - db.update_payment_method( - &(state.into()), - key_store, - existing_pm, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; + payout_data.payment_method = Some( + db.update_payment_method( + &(state.into()), + key_store, + existing_pm, + pm_update, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to add payment method in db")?, + ); }; // Store card_reference in payouts table - let updated_payout = storage::PayoutsUpdate::PayoutMethodIdUpdate { - payout_method_id: stored_resp.card_reference.to_owned(), + let payout_method_id = match &payout_data.payment_method { + Some(pm) => pm.payment_method_id.clone(), + None => stored_resp.card_reference.to_owned(), }; + + let updated_payout = storage::PayoutsUpdate::PayoutMethodIdUpdate { payout_method_id }; + payout_data.payouts = db .update_payout( payouts, @@ -636,6 +673,7 @@ pub async fn save_payout_data_to_locker( _payout_data: &mut PayoutData, _customer_id: &id_type::CustomerId, _payout_method_data: &api::PayoutMethodData, + _connector_mandate_details: Option, _merchant_account: &domain::MerchantAccount, _key_store: &domain::MerchantKeyStore, ) -> RouterResult<()> { diff --git a/crates/router/src/core/payouts/transformers.rs b/crates/router/src/core/payouts/transformers.rs index b4e41ecb09d..cb55f4629dd 100644 --- a/crates/router/src/core/payouts/transformers.rs +++ b/crates/router/src/core/payouts/transformers.rs @@ -115,6 +115,7 @@ impl phone_country_code: customer .as_ref() .and_then(|customer| customer.phone_country_code.clone()), + payout_method_id: payout.payout_method_id, } } } diff --git a/crates/router/src/core/payouts/validator.rs b/crates/router/src/core/payouts/validator.rs index c647d6eaf10..3d7d457635c 100644 --- a/crates/router/src/core/payouts/validator.rs +++ b/crates/router/src/core/payouts/validator.rs @@ -7,10 +7,17 @@ use common_utils::validation::validate_domain_against_allowed_domains; use diesel_models::generic_link::PayoutLink; use error_stack::{report, ResultExt}; pub use hyperswitch_domain_models::errors::StorageError; +use hyperswitch_domain_models::payment_methods::PaymentMethod; use router_env::{instrument, tracing, which as router_env_which, Env}; use url::Url; use super::helpers; +#[cfg(all( + any(feature = "v2", feature = "v1"), + not(feature = "payment_methods_v2"), + not(feature = "customer_v2") +))] +use crate::core::payment_methods::cards::get_pm_list_context; use crate::{ core::{ errors::{self, RouterResult}, @@ -20,6 +27,7 @@ use crate::{ routes::SessionState, types::{api::payouts, domain, storage}, utils, + utils::OptionExt, }; #[instrument(skip(db))] @@ -57,6 +65,7 @@ pub async fn validate_create_request( Option, String, Option, + Option, )> { todo!() } @@ -76,6 +85,7 @@ pub async fn validate_create_request( Option, common_utils::id_type::ProfileId, Option, + Option, )> { let merchant_id = merchant_account.get_id(); @@ -137,28 +147,6 @@ pub async fn validate_create_request( None }; - // payout_token - let payout_method_data = match (req.payout_token.as_ref(), customer.as_ref()) { - (Some(_), None) => Err(report!(errors::ApiErrorResponse::MissingRequiredField { - field_name: "customer or customer_id when payout_token is provided" - })), - (Some(payout_token), Some(customer)) => { - helpers::make_payout_method_data( - state, - req.payout_method_data.as_ref(), - Some(payout_token), - &customer.customer_id, - merchant_account.get_id(), - req.payout_type, - merchant_key_store, - None, - merchant_account.storage_scheme, - ) - .await - } - _ => Ok(None), - }?; - #[cfg(feature = "v1")] let profile_id = core_utils::get_profile_id_from_business_details( &state.into(), @@ -182,7 +170,104 @@ pub async fn validate_create_request( }) .attach_printable("Profile id is a mandatory parameter")?; - Ok((payout_id, payout_method_data, profile_id, customer)) + let payment_method: Option = + match (req.payout_token.as_ref(), req.payout_method_id.clone()) { + (Some(_), Some(_)) => Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Only one of payout_method_id or payout_token should be provided." + .to_string(), + })), + (None, Some(payment_method_id)) => match customer.as_ref() { + Some(customer) => { + let payment_method = db + .find_payment_method( + &state.into(), + merchant_key_store, + &payment_method_id, + merchant_account.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) + .attach_printable("Unable to find payment method")?; + + utils::when(payment_method.customer_id != customer.customer_id, || { + Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: "Payment method does not belong to this customer_id".to_string(), + }) + .attach_printable( + "customer_id in payment_method does not match with customer_id in request", + )) + })?; + Ok(Some(payment_method)) + } + None => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "customer_id when payment_method_id is passed", + })), + }, + _ => Ok(None), + }?; + + // payout_token + let payout_method_data = match ( + req.payout_token.as_ref(), + customer.as_ref(), + payment_method.as_ref(), + ) { + (Some(_), None, _) => Err(report!(errors::ApiErrorResponse::MissingRequiredField { + field_name: "customer or customer_id when payout_token is provided" + })), + (Some(payout_token), Some(customer), _) => { + helpers::make_payout_method_data( + state, + req.payout_method_data.as_ref(), + Some(payout_token), + &customer.customer_id, + merchant_account.get_id(), + req.payout_type, + merchant_key_store, + None, + merchant_account.storage_scheme, + ) + .await + } + (_, Some(_), Some(payment_method)) => { + match get_pm_list_context( + state, + payment_method + .payment_method + .as_ref() + .get_required_value("payment_method_id")?, + merchant_key_store, + payment_method, + None, + false, + ) + .await? + { + Some(pm) => match (pm.card_details, pm.bank_transfer_details) { + (Some(card), _) => Ok(Some(payouts::PayoutMethodData::Card( + api_models::payouts::CardPayout { + card_number: card.card_number.get_required_value("card_number")?, + card_holder_name: card.card_holder_name, + expiry_month: card.expiry_month.get_required_value("expiry_month")?, + expiry_year: card.expiry_year.get_required_value("expiry_month")?, + }, + ))), + (_, Some(bank)) => Ok(Some(payouts::PayoutMethodData::Bank(bank))), + _ => Ok(None), + }, + None => Ok(None), + } + } + _ => Ok(None), + }?; + + Ok(( + payout_id, + payout_method_data, + profile_id, + customer, + payment_method, + )) } pub fn validate_payout_link_request( diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 0f775eda855..22bc38b3d6c 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -24,7 +24,7 @@ use uuid::Uuid; use super::payments::helpers; #[cfg(feature = "payouts")] -use super::payouts::PayoutData; +use super::payouts::{helpers as payout_helpers, PayoutData}; #[cfg(feature = "payouts")] use crate::core::payments; use crate::{ @@ -150,6 +150,9 @@ pub async fn construct_payout_router_data<'a, F>( _ => None, }; + let connector_transfer_method_id = + payout_helpers::should_create_connector_transfer_method(&*payout_data, connector_data)?; + let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.get_id().to_owned(), @@ -192,6 +195,7 @@ pub async fn construct_payout_router_data<'a, F>( phone: c.phone.map(Encryptable::into_inner), phone_country_code: c.phone_country_code, }), + connector_transfer_method_id, }, response: Ok(types::PayoutsResponseData::default()), access_token: None, diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 71130f247ad..2c0d9b5b5c5 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -4,10 +4,11 @@ use actix_web::FromRequest; #[cfg(feature = "payouts")] use api_models::payouts as payout_models; use api_models::webhooks::{self, WebhookResponseTracker}; -use common_utils::{errors::ReportSwitchExt, events::ApiEventsType, ext_traits::ValueExt}; +use common_utils::{errors::ReportSwitchExt, events::ApiEventsType}; use diesel_models::ConnectorMandateReferenceId; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ + mandates::CommonMandateReference, payments::{payment_attempt::PaymentAttempt, HeaderPayload}, router_request_types::VerifyWebhookSourceRequestData, router_response_types::{VerifyWebhookSourceResponseData, VerifyWebhookStatus}, @@ -2019,14 +2020,7 @@ async fn update_connector_mandate_details( let updated_connector_mandate_details = if let Some(webhook_mandate_details) = webhook_connector_mandate_details { let mandate_details = payment_method_info - .connector_mandate_details - .clone() - .map(|val| { - val.parse_value::( - "PaymentsMandateReference", - ) - }) - .transpose() + .get_common_mandate_reference() .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to deserialize to Payment Mandate Reference")?; @@ -2035,13 +2029,9 @@ async fn update_connector_mandate_details( .clone() .get_required_value("merchant_connector_id")?; - if mandate_details - .as_ref() - .map(|details: &diesel_models::PaymentsMandateReference| { - !details.0.contains_key(&merchant_connector_account_id) - }) - .unwrap_or(true) - { + if mandate_details.payments.as_ref().map_or(true, |payments| { + !payments.0.contains_key(&merchant_connector_account_id) + }) { // Update the payment attempt to maintain consistency across tables. let (mandate_metadata, connector_mandate_request_reference_id) = payment_attempt @@ -2086,7 +2076,7 @@ async fn update_connector_mandate_details( insert_mandate_details( &payment_attempt, &webhook_mandate_details, - mandate_details, + Some(mandate_details), )? } else { logger::info!( @@ -2098,8 +2088,20 @@ async fn update_connector_mandate_details( None }; + let connector_mandate_details_value = updated_connector_mandate_details + .map(|common_mandate| { + common_mandate.get_mandate_details_value().map_err(|err| { + router_env::logger::error!( + "Failed to get get_mandate_details_value : {:?}", + err + ); + errors::ApiErrorResponse::MandateUpdateFailed + }) + }) + .transpose()?; + let pm_update = diesel_models::PaymentMethodUpdate::ConnectorNetworkTransactionIdAndMandateDetailsUpdate { - connector_mandate_details: updated_connector_mandate_details.map(masking::Secret::new), + connector_mandate_details: connector_mandate_details_value.map(masking::Secret::new), network_transaction_id: webhook_connector_network_transaction_id .map(|webhook_network_transaction_id| webhook_network_transaction_id.get_id().clone()), }; @@ -2124,8 +2126,8 @@ async fn update_connector_mandate_details( fn insert_mandate_details( payment_attempt: &PaymentAttempt, webhook_mandate_details: &hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails, - payment_method_mandate_details: Option, -) -> CustomResult, errors::ApiErrorResponse> { + payment_method_mandate_details: Option, +) -> CustomResult, errors::ApiErrorResponse> { let (mandate_metadata, connector_mandate_request_reference_id) = payment_attempt .connector_mandate_detail .clone() diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 2348fae72ab..c1d9b35be82 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1403,8 +1403,8 @@ impl ForeignFrom for payments::CaptureResponse { } #[cfg(feature = "payouts")] -impl ForeignFrom for api_enums::PaymentMethodType { - fn foreign_from(value: api_models::payouts::PayoutMethodData) -> Self { +impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentMethodType { + fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self { match value { api_models::payouts::PayoutMethodData::Bank(bank) => Self::foreign_from(bank), api_models::payouts::PayoutMethodData::Card(_) => Self::Debit, @@ -1414,8 +1414,8 @@ impl ForeignFrom for api_enums::PaymentMe } #[cfg(feature = "payouts")] -impl ForeignFrom for api_enums::PaymentMethodType { - fn foreign_from(value: api_models::payouts::Bank) -> Self { +impl ForeignFrom<&api_models::payouts::Bank> for api_enums::PaymentMethodType { + fn foreign_from(value: &api_models::payouts::Bank) -> Self { match value { api_models::payouts::Bank::Ach(_) => Self::Ach, api_models::payouts::Bank::Bacs(_) => Self::Bacs, @@ -1426,8 +1426,8 @@ impl ForeignFrom for api_enums::PaymentMethodType { } #[cfg(feature = "payouts")] -impl ForeignFrom for api_enums::PaymentMethodType { - fn foreign_from(value: api_models::payouts::Wallet) -> Self { +impl ForeignFrom<&api_models::payouts::Wallet> 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, @@ -1436,8 +1436,8 @@ impl ForeignFrom for api_enums::PaymentMethodType { } #[cfg(feature = "payouts")] -impl ForeignFrom for api_enums::PaymentMethod { - fn foreign_from(value: api_models::payouts::PayoutMethodData) -> Self { +impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_enums::PaymentMethod { + fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self { match value { api_models::payouts::PayoutMethodData::Bank(_) => Self::BankTransfer, api_models::payouts::PayoutMethodData::Card(_) => Self::Card, @@ -1446,6 +1446,17 @@ impl ForeignFrom for api_enums::PaymentMe } } +#[cfg(feature = "payouts")] +impl ForeignFrom<&api_models::payouts::PayoutMethodData> for api_models::enums::PayoutType { + fn foreign_from(value: &api_models::payouts::PayoutMethodData) -> Self { + match value { + api_models::payouts::PayoutMethodData::Bank(_) => Self::Bank, + api_models::payouts::PayoutMethodData::Card(_) => Self::Card, + api_models::payouts::PayoutMethodData::Wallet(_) => Self::Wallet, + } + } +} + #[cfg(feature = "payouts")] impl ForeignFrom for api_enums::PaymentMethod { fn foreign_from(value: api_models::enums::PayoutType) -> Self { diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index ca818e61753..fd84f5767de 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -471,6 +471,7 @@ pub trait ConnectorActions: Connector { }), vendor_details: None, priority: None, + connector_transfer_method_id: None, }, payment_info, ) From 12a2f2ad147346365f828d8fc97eb9fe49a845bb Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:04:00 +0530 Subject: [PATCH 034/133] feat(analytics): Add currency as dimension and filter for disputes (#7006) --- crates/analytics/src/disputes/core.rs | 1 + crates/analytics/src/disputes/filters.rs | 7 ++++++- crates/analytics/src/disputes/metrics.rs | 1 + .../src/disputes/metrics/dispute_status_metric.rs | 1 + .../sessionized_metrics/dispute_status_metric.rs | 1 + .../sessionized_metrics/total_amount_disputed.rs | 1 + .../sessionized_metrics/total_dispute_lost_amount.rs | 1 + .../src/disputes/metrics/total_amount_disputed.rs | 1 + .../disputes/metrics/total_dispute_lost_amount.rs | 1 + crates/analytics/src/disputes/types.rs | 6 ++++++ crates/analytics/src/sqlx.rs | 12 ++++++++++++ crates/api_models/src/analytics/disputes.rs | 10 +++++++++- 12 files changed, 41 insertions(+), 2 deletions(-) diff --git a/crates/analytics/src/disputes/core.rs b/crates/analytics/src/disputes/core.rs index 540a14104c1..c1dcbaf2b2f 100644 --- a/crates/analytics/src/disputes/core.rs +++ b/crates/analytics/src/disputes/core.rs @@ -204,6 +204,7 @@ pub async fn get_filters( .filter_map(|fil: DisputeFilterRow| match dim { DisputeDimensions::DisputeStage => fil.dispute_stage, DisputeDimensions::Connector => fil.connector, + DisputeDimensions::Currency => fil.currency.map(|i| i.as_ref().to_string()), }) .collect::>(); res.query_data.push(DisputeFilterValue { diff --git a/crates/analytics/src/disputes/filters.rs b/crates/analytics/src/disputes/filters.rs index cd60b502257..9e4aa302644 100644 --- a/crates/analytics/src/disputes/filters.rs +++ b/crates/analytics/src/disputes/filters.rs @@ -1,12 +1,16 @@ use api_models::analytics::{disputes::DisputeDimensions, Granularity, TimeRange}; use common_utils::errors::ReportSwitchExt; +use diesel_models::enums::Currency; use error_stack::ResultExt; use time::PrimitiveDateTime; use crate::{ enums::AuthInfo, query::{Aggregate, GroupByClause, QueryBuilder, QueryFilter, ToSql, Window}, - types::{AnalyticsCollection, AnalyticsDataSource, FiltersError, FiltersResult, LoadRow}, + types::{ + AnalyticsCollection, AnalyticsDataSource, DBEnumWrapper, FiltersError, FiltersResult, + LoadRow, + }, }; pub trait DisputeFilterAnalytics: LoadRow {} @@ -48,4 +52,5 @@ pub struct DisputeFilterRow { pub dispute_status: Option, pub connector_status: Option, pub dispute_stage: Option, + pub currency: Option>, } diff --git a/crates/analytics/src/disputes/metrics.rs b/crates/analytics/src/disputes/metrics.rs index 6514e5fcbe0..72bb09003d3 100644 --- a/crates/analytics/src/disputes/metrics.rs +++ b/crates/analytics/src/disputes/metrics.rs @@ -27,6 +27,7 @@ pub struct DisputeMetricRow { pub dispute_stage: Option>, pub dispute_status: Option>, pub connector: Option, + pub currency: Option>, pub total: Option, pub count: Option, #[serde(with = "common_utils::custom_serde::iso8601::option")] diff --git a/crates/analytics/src/disputes/metrics/dispute_status_metric.rs b/crates/analytics/src/disputes/metrics/dispute_status_metric.rs index ce962e284f6..b23dba4af38 100644 --- a/crates/analytics/src/disputes/metrics/dispute_status_metric.rs +++ b/crates/analytics/src/disputes/metrics/dispute_status_metric.rs @@ -97,6 +97,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs index 9a7b0535819..0d54d75aee5 100644 --- a/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/dispute_status_metric.rs @@ -97,6 +97,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs index 5c5eceb0619..bf2332ab12f 100644 --- a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_amount_disputed.rs @@ -98,6 +98,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs index d6308b09f33..5d10becbbe2 100644 --- a/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs +++ b/crates/analytics/src/disputes/metrics/sessionized_metrics/total_dispute_lost_amount.rs @@ -99,6 +99,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/metrics/total_amount_disputed.rs b/crates/analytics/src/disputes/metrics/total_amount_disputed.rs index 68c7fa6d166..fc85ee39b26 100644 --- a/crates/analytics/src/disputes/metrics/total_amount_disputed.rs +++ b/crates/analytics/src/disputes/metrics/total_amount_disputed.rs @@ -97,6 +97,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs b/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs index d14d4982701..33228cbb58b 100644 --- a/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs +++ b/crates/analytics/src/disputes/metrics/total_dispute_lost_amount.rs @@ -98,6 +98,7 @@ where DisputeMetricsBucketIdentifier::new( i.dispute_stage.as_ref().map(|i| i.0), i.connector.clone(), + i.currency.as_ref().map(|i| i.0), TimeRange { start_time: match (granularity, i.start_bucket) { (Some(g), Some(st)) => g.clip_to_start(st)?, diff --git a/crates/analytics/src/disputes/types.rs b/crates/analytics/src/disputes/types.rs index 762e8d27554..da66744aede 100644 --- a/crates/analytics/src/disputes/types.rs +++ b/crates/analytics/src/disputes/types.rs @@ -24,6 +24,12 @@ where .attach_printable("Error adding dispute stage filter")?; } + if !self.currency.is_empty() { + builder + .add_filter_in_range_clause(DisputeDimensions::Currency, &self.currency) + .attach_printable("Error adding currency filter")?; + } + Ok(()) } } diff --git a/crates/analytics/src/sqlx.rs b/crates/analytics/src/sqlx.rs index 2a94d528768..653d019adeb 100644 --- a/crates/analytics/src/sqlx.rs +++ b/crates/analytics/src/sqlx.rs @@ -933,11 +933,17 @@ impl<'a> FromRow<'a, PgRow> for super::disputes::filters::DisputeFilterRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let currency: Option> = + row.try_get("currency").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; Ok(Self { dispute_stage, dispute_status, connector, connector_status, + currency, }) } } @@ -957,6 +963,11 @@ impl<'a> FromRow<'a, PgRow> for super::disputes::metrics::DisputeMetricRow { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), })?; + let currency: Option> = + row.try_get("currency").or_else(|e| match e { + ColumnNotFound(_) => Ok(Default::default()), + e => Err(e), + })?; let total: Option = row.try_get("total").or_else(|e| match e { ColumnNotFound(_) => Ok(Default::default()), e => Err(e), @@ -976,6 +987,7 @@ impl<'a> FromRow<'a, PgRow> for super::disputes::metrics::DisputeMetricRow { dispute_stage, dispute_status, connector, + currency, total, count, start_bucket, diff --git a/crates/api_models/src/analytics/disputes.rs b/crates/api_models/src/analytics/disputes.rs index e373704b87c..179edee0413 100644 --- a/crates/api_models/src/analytics/disputes.rs +++ b/crates/api_models/src/analytics/disputes.rs @@ -4,7 +4,7 @@ use std::{ }; use super::{ForexMetric, NameDescription, TimeRange}; -use crate::enums::DisputeStage; +use crate::enums::{Currency, DisputeStage}; #[derive( Clone, @@ -58,6 +58,7 @@ pub enum DisputeDimensions { // Consult the Dashboard FE folks since these also affects the order of metrics on FE Connector, DisputeStage, + Currency, } impl From for NameDescription { @@ -82,13 +83,17 @@ impl From for NameDescription { pub struct DisputeFilters { #[serde(default)] pub dispute_stage: Vec, + #[serde(default)] pub connector: Vec, + #[serde(default)] + pub currency: Vec, } #[derive(Debug, serde::Serialize, Eq)] pub struct DisputeMetricsBucketIdentifier { pub dispute_stage: Option, pub connector: Option, + pub currency: Option, #[serde(rename = "time_range")] pub time_bucket: TimeRange, #[serde(rename = "time_bucket")] @@ -100,6 +105,7 @@ impl Hash for DisputeMetricsBucketIdentifier { fn hash(&self, state: &mut H) { self.dispute_stage.hash(state); self.connector.hash(state); + self.currency.hash(state); self.time_bucket.hash(state); } } @@ -117,11 +123,13 @@ impl DisputeMetricsBucketIdentifier { pub fn new( dispute_stage: Option, connector: Option, + currency: Option, normalized_time_range: TimeRange, ) -> Self { Self { dispute_stage, connector, + currency, time_bucket: normalized_time_range, start_time: normalized_time_range.start_time, } From 91626c0c2554126a37f2624d3b0e2b2b60be3849 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:05:36 +0530 Subject: [PATCH 035/133] build(deps): bump `openssl` from 0.10.66 to 0.10.70 (#7187) --- Cargo.lock | 8 ++++---- crates/router/Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7e2ab6eccd2..4737fff7aa2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5343,9 +5343,9 @@ dependencies = [ [[package]] name = "openssl" -version = "0.10.66" +version = "0.10.70" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" +checksum = "61cfb4e166a8bb8c9b55c500bc2308550148ece889be90f609377e58140f42c6" dependencies = [ "bitflags 2.6.0", "cfg-if 1.0.0", @@ -5375,9 +5375,9 @@ checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" -version = "0.9.103" +version = "0.9.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" +checksum = "8b22d5b84be05a8d6947c7cb71f7c849aa0f112acd4bf51c2a7c1c988ac0a9dc" dependencies = [ "cc", "libc", diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 7db0273d2e1..6257d3e0832 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -84,7 +84,7 @@ num_cpus = "1.16.0" num-traits = "0.2.19" once_cell = "1.19.0" openidconnect = "3.5.0" # TODO: remove reqwest -openssl = "0.10.64" +openssl = "0.10.70" quick-xml = { version = "0.31.0", features = ["serialize"] } rand = "0.8.5" rand_chacha = "0.3.1" From f71cc96a33ee3a9babb334c068dce7fbb3063e25 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Wed, 5 Feb 2025 19:06:31 +0530 Subject: [PATCH 036/133] fix(connector): [Deutschebank] Display deutschebank card payment method in dashboard (#7060) Co-authored-by: Debarshi Gupta --- .../connector_configs/toml/development.toml | 8 ++++++++ crates/connector_configs/toml/production.toml | 8 ++++++++ crates/connector_configs/toml/sandbox.toml | 8 ++++++++ .../src/connectors/deutschebank.rs | 19 +++++++++++++++++++ 4 files changed, 43 insertions(+) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 2ab348d5dd5..190114fee3a 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1408,6 +1408,14 @@ api_secret="Shared Secret" [deutschebank] [[deutschebank.bank_debit]] payment_method_type = "sepa" +[[deutschebank.credit]] + payment_method_type = "Visa" +[[deutschebank.credit]] + payment_method_type = "Mastercard" +[[deutschebank.debit]] + payment_method_type = "Visa" +[[deutschebank.debit]] + payment_method_type = "Mastercard" [deutschebank.connector_auth.SignatureKey] api_key="Client ID" key1="Merchant ID" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 5b963e07801..f1745587a6e 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1143,6 +1143,14 @@ type="Text" [deutschebank] [[deutschebank.bank_debit]] payment_method_type = "sepa" +[[deutschebank.credit]] + payment_method_type = "Visa" +[[deutschebank.credit]] + payment_method_type = "Mastercard" +[[deutschebank.debit]] + payment_method_type = "Visa" +[[deutschebank.debit]] + payment_method_type = "Mastercard" [deutschebank.connector_auth.SignatureKey] api_key="Client ID" key1="Merchant ID" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 50c13dc8596..2a83d62ee9c 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1357,6 +1357,14 @@ api_secret="Shared Secret" [deutschebank] [[deutschebank.bank_debit]] payment_method_type = "sepa" +[[deutschebank.credit]] + payment_method_type = "Visa" +[[deutschebank.credit]] + payment_method_type = "Mastercard" +[[deutschebank.debit]] + payment_method_type = "Visa" +[[deutschebank.debit]] + payment_method_type = "Mastercard" [deutschebank.connector_auth.SignatureKey] api_key="Client ID" key1="Merchant ID" diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index f98d6843be7..d7dcb63c476 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -1016,6 +1016,25 @@ lazy_static! { } ); + deutschebank_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails{ + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::Supported, + non_three_ds: common_enums::FeatureStatus::NotSupported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + } + ); + deutschebank_supported_payment_methods }; From ea1888677df7de60a248184389d7be30ae21fc59 Mon Sep 17 00:00:00 2001 From: Debarshi Gupta Date: Wed, 5 Feb 2025 19:07:08 +0530 Subject: [PATCH 037/133] refactor(connector): [AUTHORIZEDOTNET] Add metadata information to connector request (#7011) Co-authored-by: Debarshi Gupta Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../connector/authorizedotnet/transformers.rs | 61 +++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index d2b212e3ea0..8e0af603a3d 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use common_utils::{ errors::CustomResult, ext_traits::{Encode, ValueExt}, @@ -6,6 +8,7 @@ use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret, StrongSecret}; use rand::distributions::{Alphanumeric, DistString}; use serde::{Deserialize, Serialize}; +use serde_json::Value; use crate::{ connector::utils::{ @@ -143,12 +146,27 @@ struct TransactionRequest { #[serde(skip_serializing_if = "Option::is_none")] bill_to: Option, #[serde(skip_serializing_if = "Option::is_none")] + user_fields: Option, + #[serde(skip_serializing_if = "Option::is_none")] processing_options: Option, #[serde(skip_serializing_if = "Option::is_none")] subsequent_auth_information: Option, authorization_indicator_type: Option, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserFields { + user_field: Vec, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct UserField { + name: String, + value: String, +} + #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] enum ProfileDetails { @@ -299,6 +317,25 @@ pub enum ValidationMode { LiveMode, } +impl ForeignTryFrom for Vec { + type Error = error_stack::Report; + fn foreign_try_from(metadata: Value) -> Result { + let hashmap: BTreeMap = serde_json::from_str(&metadata.to_string()) + .change_context(errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to serialize request metadata".to_owned(), + )) + .attach_printable("")?; + let mut vector: Self = Self::new(); + for (key, value) in hashmap { + vector.push(UserField { + name: key, + value: value.to_string(), + }); + } + Ok(vector) + } +} + impl TryFrom<&types::SetupMandateRouterData> for CreateCustomerProfileRequest { type Error = error_stack::Report; fn try_from(item: &types::SetupMandateRouterData) -> Result { @@ -622,6 +659,12 @@ impl zip: address.zip.clone(), country: address.country, }), + user_fields: match item.router_data.request.metadata.clone() { + Some(metadata) => Some(UserFields { + user_field: Vec::::foreign_try_from(metadata)?, + }), + None => None, + }, processing_options: Some(ProcessingOptions { is_subsequent_auth: true, }), @@ -675,6 +718,12 @@ impl }, customer: None, bill_to: None, + user_fields: match item.router_data.request.metadata.clone() { + Some(metadata) => Some(UserFields { + user_field: Vec::::foreign_try_from(metadata)?, + }), + None => None, + }, processing_options: Some(ProcessingOptions { is_subsequent_auth: true, }), @@ -764,6 +813,12 @@ impl zip: address.zip.clone(), country: address.country, }), + user_fields: match item.router_data.request.metadata.clone() { + Some(metadata) => Some(UserFields { + user_field: Vec::::foreign_try_from(metadata)?, + }), + None => None, + }, processing_options: None, subsequent_auth_information: None, authorization_indicator_type: match item.router_data.request.capture_method { @@ -815,6 +870,12 @@ impl zip: address.zip.clone(), country: address.country, }), + user_fields: match item.router_data.request.metadata.clone() { + Some(metadata) => Some(UserFields { + user_field: Vec::::foreign_try_from(metadata)?, + }), + None => None, + }, processing_options: None, subsequent_auth_information: None, authorization_indicator_type: match item.router_data.request.capture_method { From 67ea754e383d2f9539d16f7fa40f201f177b5ea3 Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:07:11 +0530 Subject: [PATCH 038/133] feat(payments_v2): implement create and confirm intent flow (#7106) --- .../payments--create-and-confirm-intent.mdx | 3 + api-reference-v2/mint.json | 3 +- api-reference-v2/openapi_spec.json | 1727 +++++------------ crates/api_models/src/events/payment.rs | 38 +- crates/api_models/src/payments.rs | 280 +++ crates/diesel_models/src/payment_attempt.rs | 25 +- crates/diesel_models/src/schema_v2.rs | 2 +- .../hyperswitch_domain_models/src/consts.rs | 3 + .../src/payments/payment_attempt.rs | 112 +- .../src/router_data.rs | 265 ++- .../src/router_response_types.rs | 26 + crates/openapi/src/openapi_v2.rs | 5 +- crates/openapi/src/routes/payment_method.rs | 2 +- crates/openapi/src/routes/payments.rs | 58 +- crates/router/src/consts.rs | 5 +- crates/router/src/core/payments.rs | 186 ++ .../core/payments/flows/setup_mandate_flow.rs | 39 +- crates/router/src/core/payments/helpers.rs | 1 + .../payments/operations/payment_confirm.rs | 2 +- .../payments/operations/payment_create.rs | 2 +- .../payments/operations/payment_response.rs | 89 + .../router/src/core/payments/transformers.rs | 213 +- crates/router/src/core/utils.rs | 1 + crates/router/src/routes/app.rs | 12 +- crates/router/src/routes/lock_utils.rs | 1 + crates/router/src/routes/payments.rs | 51 + crates/router/src/services/api.rs | 1 - crates/router/src/types.rs | 1 + crates/router/src/types/api/payments.rs | 17 +- crates/router_env/src/logger/types.rs | 6 +- .../2024-08-28-081721_add_v2_columns/down.sql | 3 +- .../2024-08-28-081721_add_v2_columns/up.sql | 3 +- .../down.sql | 3 +- .../up.sql | 3 +- 34 files changed, 1866 insertions(+), 1322 deletions(-) create mode 100644 api-reference-v2/api-reference/payments/payments--create-and-confirm-intent.mdx rename v2_migrations/{2024-10-08-081847_drop_v1_columns => 2024-11-08-081847_drop_v1_columns}/down.sql (97%) rename v2_migrations/{2024-10-08-081847_drop_v1_columns => 2024-11-08-081847_drop_v1_columns}/up.sql (97%) diff --git a/api-reference-v2/api-reference/payments/payments--create-and-confirm-intent.mdx b/api-reference-v2/api-reference/payments/payments--create-and-confirm-intent.mdx new file mode 100644 index 00000000000..1259bd33dcd --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--create-and-confirm-intent.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payments +--- diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index d47cf7aee6d..11e716d6303 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -42,7 +42,8 @@ "api-reference/payments/payments--session-token", "api-reference/payments/payments--payment-methods-list", "api-reference/payments/payments--confirm-intent", - "api-reference/payments/payments--get" + "api-reference/payments/payments--get", + "api-reference/payments/payments--create-and-confirm-intent" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index bc593adb45b..ebb93fd8a11 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -1886,9 +1886,7 @@ "schema": { "type": "string" }, - "example": { - "X-Profile-Id": "pro_abcdefghijklmnop" - } + "example": "pro_abcdefghijklmnop" } ], "requestBody": { @@ -1959,9 +1957,7 @@ "schema": { "type": "string" }, - "example": { - "X-Profile-Id": "pro_abcdefghijklmnop" - } + "example": "pro_abcdefghijklmnop" }, { "name": "X-Client-Secret", @@ -1994,6 +1990,7 @@ "card_number": "4242424242424242" } }, + "payment_method_subtype": "credit", "payment_method_type": "card" } } @@ -2074,6 +2071,79 @@ ] } }, + "/v2/payments": { + "post": { + "tags": [ + "Payments" + ], + "summary": "Payments - Create and Confirm Intent", + "description": "**Creates and confirms a payment intent object when the amount and payment method information are passed.**\n\nYou will require the 'API - Key' from the Hyperswitch dashboard to make the call.", + "operationId": "Create and Confirm Payment Intent", + "parameters": [ + { + "name": "X-Profile-Id", + "in": "header", + "description": "Profile ID associated to the payment intent", + "required": true, + "schema": { + "type": "string" + }, + "example": "pro_abcdefghijklmnop" + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsRequest" + }, + "examples": { + "Create and confirm the payment intent with amount and card details": { + "value": { + "amount_details": { + "currency": "USD", + "order_amount": 6540 + }, + "payment_method_data": { + "card": { + "card_cvc": "123", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_number": "4242424242424242" + } + }, + "payment_method_subtype": "credit", + "payment_method_type": "card" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Payment created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentsResponse" + } + } + } + }, + "400": { + "description": "Missing Mandatory fields" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, "/v2/payments/{payment_id}/create-external-sdk-tokens": { "post": { "tags": [ @@ -2151,9 +2221,7 @@ "schema": { "type": "string" }, - "example": { - "X-Profile-Id": "pro_abcdefghijklmnop" - } + "example": "pro_abcdefghijklmnop" }, { "name": "X-Client-Secret", @@ -2296,9 +2364,7 @@ "schema": { "type": "string" }, - "example": { - "X-Profile-Id": "pro_abcdefghijklmnop" - } + "example": "pro_abcdefghijklmnop" } ], "responses": { @@ -6819,6 +6885,20 @@ "active" ] }, + "ConnectorTokenDetails": { + "type": "object", + "description": "Token information that can be used to initiate transactions by the merchant.", + "required": [ + "token" + ], + "properties": { + "token": { + "type": "string", + "description": "A token that can be used to make payments directly with the connector.", + "example": "pm_9UhMqBMEOooRIvJFFdeW" + } + } + }, "ConnectorType": { "type": "string", "description": "Type of the Connector for the financial use case. Could range from Payments to Accounting to Banking.", @@ -13198,26 +13278,6 @@ }, "additionalProperties": false }, - "PaymentListResponse": { - "type": "object", - "required": [ - "size", - "data" - ], - "properties": { - "size": { - "type": "integer", - "description": "The number of payments included in the list", - "minimum": 0 - }, - "data": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentsResponse" - } - } - } - }, "PaymentMethod": { "type": "string", "description": "Indicates the type of payment method. Eg: 'card', 'wallet', etc.", @@ -14633,6 +14693,14 @@ "example": "993672945374576J", "nullable": true }, + "connector_token_details": { + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorTokenDetails" + } + ], + "nullable": true + }, "merchant_connector_id": { "type": "string", "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment" @@ -14847,198 +14915,224 @@ }, "additionalProperties": false }, - "PaymentsCreateResponseOpenApi": { + "PaymentsDynamicTaxCalculationRequest": { + "type": "object", + "required": [ + "shipping", + "client_secret", + "payment_method_type" + ], + "properties": { + "shipping": { + "$ref": "#/components/schemas/Address" + }, + "client_secret": { + "type": "string", + "description": "Client Secret" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "session_id": { + "type": "string", + "description": "Session Id", + "nullable": true + } + } + }, + "PaymentsDynamicTaxCalculationResponse": { "type": "object", "required": [ "payment_id", - "merchant_id", - "status", - "amount", "net_amount", - "amount_capturable", - "currency", - "payment_method", - "attempt_count" + "display_amount" ], "properties": { "payment_id": { "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "maxLength": 30, - "minLength": 30 + "description": "The identifier for the payment" }, - "merchant_id": { - "type": "string", - "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", - "example": "merchant_1668273825", - "maxLength": 255 + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" }, - "status": { + "order_tax_amount": { "allOf": [ { - "$ref": "#/components/schemas/IntentStatus" + "$ref": "#/components/schemas/MinorUnit" } ], - "default": "requires_confirmation" - }, - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", - "example": 6540 - }, - "net_amount": { - "type": "integer", - "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", - "example": 6540 + "nullable": true }, "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment.", - "example": 6540, + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], "nullable": true }, - "amount_capturable": { - "type": "integer", - "format": "int64", - "description": "The maximum amount that could be captured from the payment", - "example": 6540, - "minimum": 100 + "display_amount": { + "$ref": "#/components/schemas/DisplayAmountOnSdk" + } + } + }, + "PaymentsExternalAuthenticationRequest": { + "type": "object", + "required": [ + "client_secret", + "device_channel", + "threeds_method_comp_ind" + ], + "properties": { + "client_secret": { + "type": "string", + "description": "Client Secret" }, - "amount_received": { - "type": "integer", - "format": "int64", - "description": "The amount which is already captured from the payment, this helps in the cases where merchants can't capture all capturable amount at once.", - "example": 6540, + "sdk_information": { + "allOf": [ + { + "$ref": "#/components/schemas/SdkInformation" + } + ], "nullable": true }, - "connector": { + "device_channel": { + "$ref": "#/components/schemas/DeviceChannel" + }, + "threeds_method_comp_ind": { + "$ref": "#/components/schemas/ThreeDsCompletionIndicator" + } + } + }, + "PaymentsExternalAuthenticationResponse": { + "type": "object", + "required": [ + "trans_status", + "three_ds_requestor_url" + ], + "properties": { + "trans_status": { + "$ref": "#/components/schemas/TransactionStatus" + }, + "acs_url": { "type": "string", - "description": "The connector used for the payment", - "example": "stripe", + "description": "Access Server URL to be used for challenge submission", "nullable": true }, - "client_secret": { + "challenge_request": { "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", + "description": "Challenge request which should be sent to acs_url", "nullable": true }, - "created": { + "acs_reference_number": { "type": "string", - "format": "date-time", - "description": "Time when the payment was created", - "example": "2022-09-10T10:11:12Z", + "description": "Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)", "nullable": true }, - "currency": { - "$ref": "#/components/schemas/Currency" + "acs_trans_id": { + "type": "string", + "description": "Unique identifier assigned by the ACS to identify a single transaction", + "nullable": true }, - "customer_id": { + "three_dsserver_trans_id": { "type": "string", - "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated.\nThis field will be deprecated soon. Please refer to `customer.id`", - "deprecated": true, - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 + "description": "Unique identifier assigned by the 3DS Server to identify a single transaction", + "nullable": true }, - "description": { + "acs_signed_content": { "type": "string", - "description": "A description of the payment", - "example": "It's my first payment request", + "description": "Contains the JWS object created by the ACS for the ARes(Authentication Response) message", "nullable": true }, - "refunds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RefundResponse" - }, - "description": "List of refunds that happened on this intent, as same payment intent can have multiple refund requests depending on the nature of order", - "nullable": true + "three_ds_requestor_url": { + "type": "string", + "description": "Three DS Requestor URL" + } + } + }, + "PaymentsIncrementalAuthorizationRequest": { + "type": "object", + "required": [ + "amount" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The total amount including previously authorized amount and additional amount", + "example": 6540 }, - "disputes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DisputeResponsePaymentsRetrieve" - }, - "description": "List of disputes that happened on this intent", + "reason": { + "type": "string", + "description": "Reason for incremental authorization", "nullable": true + } + } + }, + "PaymentsIntentResponse": { + "type": "object", + "required": [ + "id", + "status", + "amount_details", + "client_secret", + "profile_id", + "capture_method", + "authentication_type", + "customer_id", + "customer_present", + "setup_future_usage", + "apply_mit_exemption", + "payment_link_enabled", + "request_incremental_authorization", + "expires_on", + "request_external_three_ds_authentication" + ], + "properties": { + "id": { + "type": "string", + "description": "Global Payment Id for the payment" }, - "attempts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentAttemptResponse" - }, - "description": "List of attempts that happened on this intent", - "nullable": true + "status": { + "$ref": "#/components/schemas/IntentStatus" }, - "captures": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CaptureResponse" - }, - "description": "List of captures done on latest attempt", - "nullable": true + "amount_details": { + "$ref": "#/components/schemas/AmountDetailsResponse" }, - "mandate_id": { + "client_secret": { "type": "string", - "description": "A unique identifier to link the payment to a mandate, can be used instead of payment_method_data, in case of setting up recurring payments", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 + "description": "It's a token used for client side verification.", + "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo" }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true + "profile_id": { + "type": "string", + "description": "The identifier for the profile. This is inferred from the `x-profile-id` header" }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true + "merchant_reference_id": { + "type": "string", + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "pay_mbabizu24mvu3mela5njyhpit4", + "nullable": true, + "maxLength": 30, + "minLength": 30 }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. This parameter can only be used with confirm=true.", - "example": true, + "routing_algorithm_id": { + "type": "string", + "description": "The routing algorithm id to be used for the payment", "nullable": true }, "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" + "$ref": "#/components/schemas/CaptureMethod" }, - "payment_method_data": { + "authentication_type": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" + "$ref": "#/components/schemas/AuthenticationType" } ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "Provide a reference to a stored payment method", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true + "default": "no_three_ds" }, - "shipping": { + "billing": { "allOf": [ { "$ref": "#/components/schemas/Address" @@ -15046,7 +15140,7 @@ ], "nullable": true }, - "billing": { + "shipping": { "allOf": [ { "$ref": "#/components/schemas/Address" @@ -15054,1071 +15148,238 @@ ], "nullable": true }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", - "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", - "nullable": true - }, - "email": { + "customer_id": { "type": "string", - "description": "description: The customer's email address\nThis field will be deprecated soon. Please refer to `customer.email` object", - "deprecated": true, - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 }, - "name": { - "type": "string", - "description": "description: The customer's name\nThis field will be deprecated soon. Please refer to `customer.name` object", - "deprecated": true, - "example": "John Test", - "nullable": true, - "maxLength": 255 + "customer_present": { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" }, - "phone": { + "description": { "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon. Please refer to `customer.phone` object", - "deprecated": true, - "example": "9123456789", - "nullable": true, - "maxLength": 255 + "description": "A description for the payment", + "example": "It's my first payment request", + "nullable": true }, "return_url": { "type": "string", - "description": "The URL to redirect after the completion of the operation", + "description": "The URL to which you want the user to be redirected after the completion of the payment operation", "example": "https://hyperswitch.io", "nullable": true }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true + "setup_future_usage": { + "$ref": "#/components/schemas/FutureUsage" + }, + "apply_mit_exemption": { + "$ref": "#/components/schemas/MitExemptionRequest" }, - "statement_descriptor_name": { + "statement_descriptor": { "type": "string", "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", "example": "Hyperswitch Router", "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 255 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "next_action": { - "allOf": [ - { - "$ref": "#/components/schemas/NextActionData" - } - ], - "nullable": true + "maxLength": 22 }, - "cancellation_reason": { - "type": "string", - "description": "If the payment was cancelled the reason will be provided here", + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", + "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", "nullable": true }, - "error_code": { - "type": "string", - "description": "If there was an error while calling the connectors the code is received here", - "example": "E0001", + "allowed_payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", "nullable": true }, - "error_message": { - "type": "string", - "description": "If there was an error while calling the connector the error message is received here", - "example": "Failed while verifying the card", + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, - "payment_experience": { + "connector_metadata": { "allOf": [ { - "$ref": "#/components/schemas/PaymentExperience" + "$ref": "#/components/schemas/ConnectorMetadata" } ], "nullable": true }, - "payment_method_type": { + "feature_metadata": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/FeatureMetadata" } ], "nullable": true }, - "connector_label": { - "type": "string", - "description": "The connector used for this payment along with the country and business details", - "example": "stripe_US_food", - "nullable": true + "payment_link_enabled": { + "$ref": "#/components/schemas/EnablePaymentLinkRequest" }, - "business_country": { + "payment_link_config": { "allOf": [ { - "$ref": "#/components/schemas/CountryAlpha2" + "$ref": "#/components/schemas/PaymentLinkConfigRequest" } ], "nullable": true }, - "business_label": { - "type": "string", - "description": "The business label of merchant for this payment", - "nullable": true + "request_incremental_authorization": { + "$ref": "#/components/schemas/RequestIncrementalAuthorization" }, - "business_sub_label": { + "expires_on": { "type": "string", - "description": "The business_sub_label for this payment", - "nullable": true + "format": "date-time", + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Allowed Payment Method Types for a given PaymentIntent", + "frm_metadata": { + "type": "object", + "description": "Additional data related to some frm(Fraud Risk Management) connectors", "nullable": true }, - "ephemeral_key": { - "allOf": [ - { - "$ref": "#/components/schemas/EphemeralKeyCreateResponse" - } - ], - "nullable": true - }, - "manual_retry_allowed": { - "type": "boolean", - "description": "If true the payment can be retried with same or different payment method which means the confirm call can be made again.", - "nullable": true - }, - "connector_transaction_id": { - "type": "string", - "description": "A unique identifier for a payment provided by the connector", - "example": "993672945374576J", - "nullable": true - }, - "frm_message": { - "allOf": [ - { - "$ref": "#/components/schemas/FrmMessage" - } - ], - "nullable": true - }, - "metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "feature_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/FeatureMetadata" - } - ], - "nullable": true - }, - "reference_id": { - "type": "string", - "description": "reference(Identifier) to the payment at connector side", - "example": "993672945374576J", - "nullable": true - }, - "payment_link": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentLinkResponse" - } - ], - "nullable": true - }, - "profile_id": { - "type": "string", - "description": "The business profile that is associated with this payment", - "nullable": true - }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/RequestSurchargeDetails" - } - ], - "nullable": true - }, - "attempt_count": { - "type": "integer", - "format": "int32", - "description": "Total number of attempts associated with this payment" - }, - "merchant_decision": { - "type": "string", - "description": "Denotes the action(approve or reject) taken by merchant in case of manual review. Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment", - "nullable": true - }, - "merchant_connector_id": { - "type": "string", - "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", - "nullable": true - }, - "incremental_authorization_allowed": { - "type": "boolean", - "description": "If true, incremental authorization can be performed on this payment, in case the funds authorized initially fall short.", - "nullable": true - }, - "authorization_count": { - "type": "integer", - "format": "int32", - "description": "Total number of authorizations happened in an incremental_authorization payment", - "nullable": true - }, - "incremental_authorizations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IncrementalAuthorizationResponse" - }, - "description": "List of incremental authorizations happened to the payment", - "nullable": true - }, - "external_authentication_details": { - "allOf": [ - { - "$ref": "#/components/schemas/ExternalAuthenticationDetailsResponse" - } - ], - "nullable": true - }, - "external_3ds_authentication_attempted": { - "type": "boolean", - "description": "Flag indicating if external 3ds authentication is made or not", - "nullable": true - }, - "expires_on": { - "type": "string", - "format": "date-time", - "description": "Date Time for expiry of the payment", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "fingerprint": { - "type": "string", - "description": "Payment Fingerprint, to identify a particular card.\nIt is a 20 character long alphanumeric code.", - "nullable": true - }, - "browser_info": { - "allOf": [ - { - "$ref": "#/components/schemas/BrowserInformation" - } - ], - "nullable": true - }, - "payment_method_id": { - "type": "string", - "description": "Identifier for Payment Method used for the payment", - "nullable": true - }, - "payment_method_status": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodStatus" - } - ], - "nullable": true - }, - "updated": { - "type": "string", - "format": "date-time", - "description": "Date time at which payment was updated", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "split_payments": { - "allOf": [ - { - "$ref": "#/components/schemas/SplitPaymentsResponse" - } - ], - "nullable": true - }, - "frm_metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", - "nullable": true - }, - "merchant_order_reference_id": { - "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 - }, - "order_tax_amount": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" - } - ], - "nullable": true - }, - "connector_mandate_id": { - "type": "string", - "description": "Connector Identifier for the payment method", - "nullable": true - } - } - }, - "PaymentsDynamicTaxCalculationRequest": { - "type": "object", - "required": [ - "shipping", - "client_secret", - "payment_method_type" - ], - "properties": { - "shipping": { - "$ref": "#/components/schemas/Address" - }, - "client_secret": { - "type": "string", - "description": "Client Secret" - }, - "payment_method_type": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "session_id": { - "type": "string", - "description": "Session Id", - "nullable": true - } - } - }, - "PaymentsDynamicTaxCalculationResponse": { - "type": "object", - "required": [ - "payment_id", - "net_amount", - "display_amount" - ], - "properties": { - "payment_id": { - "type": "string", - "description": "The identifier for the payment" - }, - "net_amount": { - "$ref": "#/components/schemas/MinorUnit" - }, - "order_tax_amount": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" - } - ], - "nullable": true - }, - "shipping_cost": { - "allOf": [ - { - "$ref": "#/components/schemas/MinorUnit" - } - ], - "nullable": true - }, - "display_amount": { - "$ref": "#/components/schemas/DisplayAmountOnSdk" - } - } - }, - "PaymentsExternalAuthenticationRequest": { - "type": "object", - "required": [ - "client_secret", - "device_channel", - "threeds_method_comp_ind" - ], - "properties": { - "client_secret": { - "type": "string", - "description": "Client Secret" - }, - "sdk_information": { - "allOf": [ - { - "$ref": "#/components/schemas/SdkInformation" - } - ], - "nullable": true - }, - "device_channel": { - "$ref": "#/components/schemas/DeviceChannel" - }, - "threeds_method_comp_ind": { - "$ref": "#/components/schemas/ThreeDsCompletionIndicator" + "request_external_three_ds_authentication": { + "$ref": "#/components/schemas/External3dsAuthenticationRequest" } - } + }, + "additionalProperties": false }, - "PaymentsExternalAuthenticationResponse": { + "PaymentsRequest": { "type": "object", "required": [ - "trans_status", - "three_ds_requestor_url" + "amount_details", + "customer_id", + "payment_method_data", + "payment_method_type", + "payment_method_subtype" ], "properties": { - "trans_status": { - "$ref": "#/components/schemas/TransactionStatus" - }, - "acs_url": { - "type": "string", - "description": "Access Server URL to be used for challenge submission", - "nullable": true - }, - "challenge_request": { - "type": "string", - "description": "Challenge request which should be sent to acs_url", - "nullable": true - }, - "acs_reference_number": { - "type": "string", - "description": "Unique identifier assigned by the EMVCo(Europay, Mastercard and Visa)", - "nullable": true - }, - "acs_trans_id": { - "type": "string", - "description": "Unique identifier assigned by the ACS to identify a single transaction", - "nullable": true - }, - "three_dsserver_trans_id": { - "type": "string", - "description": "Unique identifier assigned by the 3DS Server to identify a single transaction", - "nullable": true - }, - "acs_signed_content": { - "type": "string", - "description": "Contains the JWS object created by the ACS for the ARes(Authentication Response) message", - "nullable": true - }, - "three_ds_requestor_url": { - "type": "string", - "description": "Three DS Requestor URL" - } - } - }, - "PaymentsIncrementalAuthorizationRequest": { - "type": "object", - "required": [ - "amount" - ], - "properties": { - "amount": { - "type": "integer", - "format": "int64", - "description": "The total amount including previously authorized amount and additional amount", - "example": 6540 - }, - "reason": { - "type": "string", - "description": "Reason for incremental authorization", - "nullable": true - } - } - }, - "PaymentsIntentResponse": { - "type": "object", - "required": [ - "id", - "status", - "amount_details", - "client_secret", - "profile_id", - "capture_method", - "authentication_type", - "customer_id", - "customer_present", - "setup_future_usage", - "apply_mit_exemption", - "payment_link_enabled", - "request_incremental_authorization", - "expires_on", - "request_external_three_ds_authentication" - ], - "properties": { - "id": { - "type": "string", - "description": "Global Payment Id for the payment" - }, - "status": { - "$ref": "#/components/schemas/IntentStatus" - }, - "amount_details": { - "$ref": "#/components/schemas/AmountDetailsResponse" - }, - "client_secret": { - "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo" - }, - "profile_id": { - "type": "string", - "description": "The identifier for the profile. This is inferred from the `x-profile-id` header" - }, - "merchant_reference_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "nullable": true, - "maxLength": 30, - "minLength": 30 - }, - "routing_algorithm_id": { - "type": "string", - "description": "The routing algorithm id to be used for the payment", - "nullable": true - }, - "capture_method": { - "$ref": "#/components/schemas/CaptureMethod" - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "no_three_ds" - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer", - "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", - "maxLength": 64, - "minLength": 32 - }, - "customer_present": { - "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" - }, - "description": { - "type": "string", - "description": "A description for the payment", - "example": "It's my first payment request", - "nullable": true - }, - "return_url": { - "type": "string", - "description": "The URL to which you want the user to be redirected after the completion of the payment operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "setup_future_usage": { - "$ref": "#/components/schemas/FutureUsage" - }, - "apply_mit_exemption": { - "$ref": "#/components/schemas/MitExemptionRequest" - }, - "statement_descriptor": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 22 - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", - "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", - "nullable": true - }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", - "nullable": true - }, - "metadata": { - "type": "object", - "description": "Metadata is useful for storing additional, unstructured information on an object.", - "nullable": true - }, - "connector_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/ConnectorMetadata" - } - ], - "nullable": true - }, - "feature_metadata": { - "allOf": [ - { - "$ref": "#/components/schemas/FeatureMetadata" - } - ], - "nullable": true - }, - "payment_link_enabled": { - "$ref": "#/components/schemas/EnablePaymentLinkRequest" - }, - "payment_link_config": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentLinkConfigRequest" - } - ], - "nullable": true - }, - "request_incremental_authorization": { - "$ref": "#/components/schemas/RequestIncrementalAuthorization" - }, - "expires_on": { - "type": "string", - "format": "date-time", - "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds" - }, - "frm_metadata": { - "type": "object", - "description": "Additional data related to some frm(Fraud Risk Management) connectors", - "nullable": true - }, - "request_external_three_ds_authentication": { - "$ref": "#/components/schemas/External3dsAuthenticationRequest" - } - }, - "additionalProperties": false - }, - "PaymentsResponse": { - "type": "object", - "required": [ - "payment_id", - "merchant_id", - "status", - "amount", - "net_amount", - "amount_capturable", - "currency", - "payment_method", - "attempt_count" - ], - "properties": { - "payment_id": { - "type": "string", - "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", - "example": "pay_mbabizu24mvu3mela5njyhpit4", - "maxLength": 30, - "minLength": 30 - }, - "merchant_id": { - "type": "string", - "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", - "example": "merchant_1668273825", - "maxLength": 255 - }, - "status": { - "allOf": [ - { - "$ref": "#/components/schemas/IntentStatus" - } - ], - "default": "requires_confirmation" - }, - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", - "example": 6540 - }, - "net_amount": { - "type": "integer", - "format": "int64", - "description": "The payment net amount. net_amount = amount + surcharge_details.surcharge_amount + surcharge_details.tax_amount + shipping_cost + order_tax_amount,\nIf no surcharge_details, shipping_cost, order_tax_amount, net_amount = amount", - "example": 6540 - }, - "shipping_cost": { - "type": "integer", - "format": "int64", - "description": "The shipping cost for the payment.", - "example": 6540, - "nullable": true - }, - "amount_capturable": { - "type": "integer", - "format": "int64", - "description": "The maximum amount that could be captured from the payment", - "example": 6540, - "minimum": 100 - }, - "amount_received": { - "type": "integer", - "format": "int64", - "description": "The amount which is already captured from the payment, this helps in the cases where merchants can't capture all capturable amount at once.", - "example": 6540, - "nullable": true - }, - "connector": { - "type": "string", - "description": "The connector used for the payment", - "example": "stripe", - "nullable": true - }, - "client_secret": { - "type": "string", - "description": "It's a token used for client side verification.", - "example": "pay_U42c409qyHwOkWo3vK60_secret_el9ksDkiB8hi6j9N78yo", - "nullable": true - }, - "created": { - "type": "string", - "format": "date-time", - "description": "Time when the payment was created", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "currency": { - "$ref": "#/components/schemas/Currency" - }, - "customer_id": { - "type": "string", - "description": "The identifier for the customer object. If not provided the customer ID will be autogenerated.\nThis field will be deprecated soon. Please refer to `customer.id`", - "deprecated": true, - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "customer": { - "allOf": [ - { - "$ref": "#/components/schemas/CustomerDetailsResponse" - } - ], - "nullable": true - }, - "description": { - "type": "string", - "description": "A description of the payment", - "example": "It's my first payment request", - "nullable": true - }, - "refunds": { - "type": "array", - "items": { - "$ref": "#/components/schemas/RefundResponse" - }, - "description": "List of refunds that happened on this intent, as same payment intent can have multiple refund requests depending on the nature of order", - "nullable": true - }, - "disputes": { - "type": "array", - "items": { - "$ref": "#/components/schemas/DisputeResponsePaymentsRetrieve" - }, - "description": "List of disputes that happened on this intent", - "nullable": true - }, - "attempts": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentAttemptResponse" - }, - "description": "List of attempts that happened on this intent", - "nullable": true - }, - "captures": { - "type": "array", - "items": { - "$ref": "#/components/schemas/CaptureResponse" - }, - "description": "List of captures done on latest attempt", - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate, can be used instead of payment_method_data, in case of setting up recurring payments", - "example": "mandate_iwer89rnjef349dni3", - "nullable": true, - "maxLength": 255 - }, - "mandate_data": { - "allOf": [ - { - "$ref": "#/components/schemas/MandateData" - } - ], - "nullable": true - }, - "setup_future_usage": { - "allOf": [ - { - "$ref": "#/components/schemas/FutureUsage" - } - ], - "nullable": true - }, - "off_session": { - "type": "boolean", - "description": "Set to true to indicate that the customer is not in your checkout flow during this payment, and therefore is unable to authenticate. This parameter is intended for scenarios where you collect card details and charge them later. This parameter can only be used with confirm=true.", - "example": true, - "nullable": true - }, - "capture_on": { - "type": "string", - "format": "date-time", - "description": "A timestamp (ISO 8601 code) that determines when the payment should be captured.\nProviding this field will automatically set `capture` to true", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "capture_method": { - "allOf": [ - { - "$ref": "#/components/schemas/CaptureMethod" - } - ], - "nullable": true - }, - "payment_method": { - "$ref": "#/components/schemas/PaymentMethod" - }, - "payment_method_data": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" - } - ], - "nullable": true - }, - "payment_token": { - "type": "string", - "description": "Provide a reference to a stored payment method", - "example": "187282ab-40ef-47a9-9206-5099ba31e432", - "nullable": true - }, - "shipping": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "billing": { - "allOf": [ - { - "$ref": "#/components/schemas/Address" - } - ], - "nullable": true - }, - "order_details": { - "type": "array", - "items": { - "$ref": "#/components/schemas/OrderDetailsWithAmount" - }, - "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", - "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", - "nullable": true - }, - "email": { - "type": "string", - "description": "description: The customer's email address\nThis field will be deprecated soon. Please refer to `customer.email` object", - "deprecated": true, - "example": "johntest@test.com", - "nullable": true, - "maxLength": 255 - }, - "name": { - "type": "string", - "description": "description: The customer's name\nThis field will be deprecated soon. Please refer to `customer.name` object", - "deprecated": true, - "example": "John Test", - "nullable": true, - "maxLength": 255 - }, - "phone": { - "type": "string", - "description": "The customer's phone number\nThis field will be deprecated soon. Please refer to `customer.phone` object", - "deprecated": true, - "example": "9123456789", - "nullable": true, - "maxLength": 255 - }, - "return_url": { - "type": "string", - "description": "The URL to redirect after the completion of the operation", - "example": "https://hyperswitch.io", - "nullable": true - }, - "authentication_type": { - "allOf": [ - { - "$ref": "#/components/schemas/AuthenticationType" - } - ], - "default": "three_ds", - "nullable": true - }, - "statement_descriptor_name": { - "type": "string", - "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", - "example": "Hyperswitch Router", - "nullable": true, - "maxLength": 255 - }, - "statement_descriptor_suffix": { - "type": "string", - "description": "Provides information about a card payment that customers see on their statements. Concatenated with the prefix (shortened descriptor) or statement descriptor that’s set on the account to form the complete statement descriptor. Maximum 255 characters for the concatenated descriptor.", - "example": "Payment for shoes purchase", - "nullable": true, - "maxLength": 255 - }, - "next_action": { - "allOf": [ - { - "$ref": "#/components/schemas/NextActionData" - } - ], - "nullable": true - }, - "cancellation_reason": { - "type": "string", - "description": "If the payment was cancelled the reason will be provided here", - "nullable": true - }, - "error_code": { - "type": "string", - "description": "If there was an error while calling the connectors the code is received here", - "example": "E0001", - "nullable": true - }, - "error_message": { - "type": "string", - "description": "If there was an error while calling the connector the error message is received here", - "example": "Failed while verifying the card", - "nullable": true + "amount_details": { + "$ref": "#/components/schemas/AmountDetails" }, - "unified_code": { + "merchant_reference_id": { "type": "string", - "description": "error code unified across the connectors is received here if there was an error while calling connector", - "nullable": true + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "pay_mbabizu24mvu3mela5njyhpit4", + "nullable": true, + "maxLength": 30, + "minLength": 30 }, - "unified_message": { + "routing_algorithm_id": { "type": "string", - "description": "error message unified across the connectors is received here if there was an error while calling connector", + "description": "The routing algorithm id to be used for the payment", "nullable": true }, - "payment_experience": { + "capture_method": { "allOf": [ { - "$ref": "#/components/schemas/PaymentExperience" + "$ref": "#/components/schemas/CaptureMethod" } ], "nullable": true }, - "payment_method_type": { + "authentication_type": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/AuthenticationType" } ], + "default": "no_three_ds", "nullable": true }, - "connector_label": { - "type": "string", - "description": "The connector used for this payment along with the country and business details", - "example": "stripe_US_food", + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], "nullable": true }, - "business_country": { + "shipping": { "allOf": [ { - "$ref": "#/components/schemas/CountryAlpha2" + "$ref": "#/components/schemas/Address" } ], "nullable": true }, - "business_label": { + "customer_id": { "type": "string", - "description": "The business label of merchant for this payment", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "customer_present": { + "allOf": [ + { + "$ref": "#/components/schemas/PresenceOfCustomerDuringPayment" + } + ], "nullable": true }, - "business_sub_label": { + "description": { "type": "string", - "description": "The business_sub_label for this payment", + "description": "A description for the payment", + "example": "It's my first payment request", "nullable": true }, - "allowed_payment_method_types": { - "type": "array", - "items": { - "$ref": "#/components/schemas/PaymentMethodType" - }, - "description": "Allowed Payment Method Types for a given PaymentIntent", + "return_url": { + "type": "string", + "description": "The URL to which you want the user to be redirected after the completion of the payment operation", + "example": "https://hyperswitch.io", "nullable": true }, - "ephemeral_key": { + "setup_future_usage": { "allOf": [ { - "$ref": "#/components/schemas/EphemeralKeyCreateResponse" + "$ref": "#/components/schemas/FutureUsage" } ], "nullable": true }, - "manual_retry_allowed": { - "type": "boolean", - "description": "If true the payment can be retried with same or different payment method which means the confirm call can be made again.", + "apply_mit_exemption": { + "allOf": [ + { + "$ref": "#/components/schemas/MitExemptionRequest" + } + ], "nullable": true }, - "connector_transaction_id": { + "statement_descriptor": { "type": "string", - "description": "A unique identifier for a payment provided by the connector", - "example": "993672945374576J", + "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", + "example": "Hyperswitch Router", + "nullable": true, + "maxLength": 22 + }, + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount", + "example": "[{\n \"product_name\": \"Apple iPhone 16\",\n \"quantity\": 1,\n \"amount\" : 69000\n \"product_img_link\" : \"https://dummy-img-link.com\"\n }]", "nullable": true }, - "frm_message": { - "allOf": [ - { - "$ref": "#/components/schemas/FrmMessage" - } - ], + "allowed_payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "description": "Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent", "nullable": true }, "metadata": { "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. Metadata is useful for storing additional, structured information on an object.", + "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true }, "connector_metadata": { @@ -16137,151 +15398,189 @@ ], "nullable": true }, - "reference_id": { - "type": "string", - "description": "reference(Identifier) to the payment at connector side", - "example": "993672945374576J", - "nullable": true - }, - "payment_link": { + "payment_link_enabled": { "allOf": [ { - "$ref": "#/components/schemas/PaymentLinkResponse" + "$ref": "#/components/schemas/EnablePaymentLinkRequest" } ], "nullable": true }, - "profile_id": { - "type": "string", - "description": "The business profile that is associated with this payment", + "payment_link_config": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentLinkConfigRequest" + } + ], "nullable": true }, - "surcharge_details": { + "request_incremental_authorization": { "allOf": [ { - "$ref": "#/components/schemas/RequestSurchargeDetails" + "$ref": "#/components/schemas/RequestIncrementalAuthorization" } ], "nullable": true }, - "attempt_count": { + "session_expiry": { "type": "integer", "format": "int32", - "description": "Total number of attempts associated with this payment" + "description": "Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config\n(900) for 15 mins", + "example": 900, + "nullable": true, + "minimum": 0 }, - "merchant_decision": { - "type": "string", - "description": "Denotes the action(approve or reject) taken by merchant in case of manual review. Manual review can occur when the transaction is marked as risky by the frm_processor, payment processor or when there is underpayment/over payment incase of crypto payment", + "frm_metadata": { + "type": "object", + "description": "Additional data related to some frm(Fraud Risk Management) connectors", "nullable": true }, - "merchant_connector_id": { - "type": "string", - "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "request_external_three_ds_authentication": { + "allOf": [ + { + "$ref": "#/components/schemas/External3dsAuthenticationRequest" + } + ], "nullable": true }, - "incremental_authorization_allowed": { - "type": "boolean", - "description": "If true, incremental authorization can be performed on this payment, in case the funds authorized initially fall short.", - "nullable": true + "payment_method_data": { + "$ref": "#/components/schemas/PaymentMethodDataRequest" }, - "authorization_count": { - "type": "integer", - "format": "int32", - "description": "Total number of authorizations happened in an incremental_authorization payment", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" }, - "incremental_authorizations": { - "type": "array", - "items": { - "$ref": "#/components/schemas/IncrementalAuthorizationResponse" - }, - "description": "List of incremental authorizations happened to the payment", - "nullable": true + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "external_authentication_details": { + "customer_acceptance": { "allOf": [ { - "$ref": "#/components/schemas/ExternalAuthenticationDetailsResponse" + "$ref": "#/components/schemas/CustomerAcceptance" } ], "nullable": true }, - "external_3ds_authentication_attempted": { - "type": "boolean", - "description": "Flag indicating if external 3ds authentication is made or not", + "browser_info": { + "allOf": [ + { + "$ref": "#/components/schemas/BrowserInformation" + } + ], "nullable": true + } + }, + "additionalProperties": false + }, + "PaymentsResponse": { + "type": "object", + "required": [ + "id", + "status", + "amount", + "customer_id", + "connector", + "client_secret", + "created", + "payment_method_type", + "payment_method_subtype", + "merchant_connector_id" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the payment. This ensures idempotency for multiple payments\nthat have been done by a single merchant.", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 }, - "expires_on": { + "status": { + "$ref": "#/components/schemas/IntentStatus" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAmountDetailsResponse" + }, + "customer_id": { "type": "string", - "format": "date-time", - "description": "Date Time for expiry of the payment", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 }, - "fingerprint": { + "connector": { "type": "string", - "description": "Payment Fingerprint, to identify a particular card.\nIt is a 20 character long alphanumeric code.", - "nullable": true + "description": "The connector used for the payment", + "example": "stripe" }, - "browser_info": { + "client_secret": { + "type": "string", + "description": "It's a token used for client side verification." + }, + "created": { + "type": "string", + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" + }, + "payment_method_data": { "allOf": [ { - "$ref": "#/components/schemas/BrowserInformation" + "$ref": "#/components/schemas/PaymentMethodDataResponseWithBilling" } ], "nullable": true }, - "payment_method_id": { - "type": "string", - "description": "Identifier for Payment Method used for the payment", - "nullable": true + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" }, - "payment_method_status": { + "next_action": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodStatus" + "$ref": "#/components/schemas/NextActionData" } ], "nullable": true }, - "updated": { + "connector_transaction_id": { "type": "string", - "format": "date-time", - "description": "Date time at which payment was updated", - "example": "2022-09-10T10:11:12Z", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", + "nullable": true + }, + "connector_reference_id": { + "type": "string", + "description": "reference(Identifier) to the payment at connector side", + "example": "993672945374576J", "nullable": true }, - "split_payments": { + "connector_token_details": { "allOf": [ { - "$ref": "#/components/schemas/SplitPaymentsResponse" + "$ref": "#/components/schemas/ConnectorTokenDetails" } ], "nullable": true }, - "frm_metadata": { - "type": "object", - "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", - "nullable": true - }, - "merchant_order_reference_id": { + "merchant_connector_id": { "type": "string", - "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", - "example": "Custom_Order_id_123", - "nullable": true, - "maxLength": 255 + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment" }, - "order_tax_amount": { + "browser_info": { "allOf": [ { - "$ref": "#/components/schemas/MinorUnit" + "$ref": "#/components/schemas/BrowserInformation" } ], "nullable": true }, - "connector_mandate_id": { - "type": "string", - "description": "Connector Identifier for the payment method", + "error": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorDetails" + } + ], "nullable": true } } diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index e6de6190b83..c365445e102 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -3,13 +3,15 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; #[cfg(feature = "v2")] use super::{ PaymentStartRedirectionRequest, PaymentsConfirmIntentResponse, PaymentsCreateIntentRequest, - PaymentsGetIntentRequest, PaymentsIntentResponse, + PaymentsGetIntentRequest, PaymentsIntentResponse, PaymentsRequest, }; #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") ))] use crate::payment_methods::CustomerPaymentMethodsListResponse; +#[cfg(feature = "v1")] +use crate::payments::{PaymentListResponse, PaymentListResponseV2}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::{events, payment_methods::CustomerPaymentMethodsListResponse}; use crate::{ @@ -23,14 +25,14 @@ use crate::{ payments::{ self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, - PaymentListResponse, PaymentListResponseV2, PaymentsAggregateResponse, - PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, - PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, - PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, - PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, - PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, - PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, - PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, + PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, + PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, + PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, + PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, + PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, + PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, + PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsResponse, + PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, RedirectionResponse, }, }; @@ -150,6 +152,22 @@ impl ApiEventMetric for PaymentsCreateIntentRequest { } } +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsRequest { + fn get_api_event_type(&self) -> Option { + None + } +} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentsResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Payment { + payment_id: self.id.clone(), + }) + } +} + #[cfg(feature = "v2")] impl ApiEventMetric for PaymentsGetIntentRequest { fn get_api_event_type(&self) -> Option { @@ -355,12 +373,14 @@ impl ApiEventMetric for PaymentListConstraints { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListResponseV2 { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 3a6fe000afe..4a1fa8b408e 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4414,6 +4414,7 @@ pub struct ReceiverDetails { amount_remaining: Option, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema, router_derive::PolymorphicSchema)] #[generate_schemas(PaymentsCreateResponseOpenApi)] pub struct PaymentsResponse { @@ -4787,6 +4788,271 @@ pub struct PaymentsConfirmIntentRequest { pub browser_info: Option, } +// This struct contains the union of fields in `PaymentsCreateIntentRequest` and +// `PaymentsConfirmIntentRequest` +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(deny_unknown_fields)] +#[cfg(feature = "v2")] +pub struct PaymentsRequest { + /// The amount details for the payment + pub amount_details: AmountDetails, + + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + value_type = Option, + min_length = 30, + max_length = 30, + example = "pay_mbabizu24mvu3mela5njyhpit4" + )] + pub merchant_reference_id: Option, + + /// The routing algorithm id to be used for the payment + #[schema(value_type = Option)] + pub routing_algorithm_id: Option, + + #[schema(value_type = Option, example = "automatic")] + pub capture_method: Option, + + #[schema(value_type = Option, example = "no_three_ds", default = "no_three_ds")] + pub authentication_type: Option, + + /// The billing details of the payment. This address will be used for invoicing. + pub billing: Option
, + + /// The shipping address for the payment + pub shipping: Option
, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, + + /// Set to `present` to indicate that the customer is in your checkout flow during this payment, and therefore is able to authenticate. This parameter should be `absent` when merchant's doing merchant initiated payments and customer is not present while doing the payment. + #[schema(example = "present", value_type = Option)] + pub customer_present: Option, + + /// A description for the payment + #[schema(example = "It's my first payment request", value_type = Option)] + pub description: Option, + + /// The URL to which you want the user to be redirected after the completion of the payment operation + #[schema(value_type = Option, example = "https://hyperswitch.io")] + pub return_url: Option, + + #[schema(value_type = Option, example = "off_session")] + pub setup_future_usage: Option, + + /// Apply MIT exemption for a payment + #[schema(value_type = Option)] + pub apply_mit_exemption: Option, + + /// For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters. + #[schema(max_length = 22, example = "Hyperswitch Router", value_type = Option)] + pub statement_descriptor: Option, + + /// Use this object to capture the details about the different products for which the payment is being made. The sum of amount across different products here should be equal to the overall payment amount + #[schema(value_type = Option>, example = r#"[{ + "product_name": "Apple iPhone 16", + "quantity": 1, + "amount" : 69000 + "product_img_link" : "https://dummy-img-link.com" + }]"#)] + pub order_details: Option>, + + /// Use this parameter to restrict the Payment Method Types to show for a given PaymentIntent + #[schema(value_type = Option>)] + pub allowed_payment_method_types: Option>, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] + pub metadata: Option, + + /// Some connectors like Apple pay, Airwallex and Noon might require some additional information, find specific details in the child attributes below. + pub connector_metadata: Option, + + /// Additional data that might be required by hyperswitch based on the requested features by the merchants. + pub feature_metadata: Option, + + /// Whether to generate the payment link for this payment or not (if applicable) + #[schema(value_type = Option)] + pub payment_link_enabled: Option, + + /// Configure a custom payment link for the particular payment + #[schema(value_type = Option)] + pub payment_link_config: Option, + + ///Request an incremental authorization, i.e., increase the authorized amount on a confirmed payment before you capture it. + #[schema(value_type = Option)] + pub request_incremental_authorization: Option, + + ///Will be used to expire client secret after certain amount of time to be supplied in seconds, if not sent it will be taken from profile config + ///(900) for 15 mins + #[schema(example = 900)] + pub session_expiry: Option, + + /// Additional data related to some frm(Fraud Risk Management) connectors + #[schema(value_type = Option, example = r#"{ "coverage_request" : "fraud", "fulfillment_method" : "delivery" }"#)] + pub frm_metadata: Option, + + /// Whether to perform external authentication (if applicable) + #[schema(value_type = Option)] + pub request_external_three_ds_authentication: + Option, + + /// The payment instrument data to be used for the payment + pub payment_method_data: PaymentMethodDataRequest, + + /// The payment method type to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethod, example = "card")] + pub payment_method_type: api_enums::PaymentMethod, + + /// The payment method subtype to be used for the payment. This should match with the `payment_method_data` provided + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// This "CustomerAcceptance" object is passed during Payments-Confirm request, it enlists the type, time, and mode of acceptance properties related to an acceptance done by the customer. The customer_acceptance sub object is usually passed by the SDK or client. + #[schema(value_type = Option)] + pub customer_acceptance: Option, + + /// Additional details required by 3DS 2.0 + #[schema(value_type = Option)] + pub browser_info: Option, +} + +#[cfg(feature = "v2")] +impl From<&PaymentsRequest> for PaymentsCreateIntentRequest { + fn from(request: &PaymentsRequest) -> Self { + Self { + amount_details: request.amount_details.clone(), + merchant_reference_id: request.merchant_reference_id.clone(), + routing_algorithm_id: request.routing_algorithm_id.clone(), + capture_method: request.capture_method, + authentication_type: request.authentication_type, + billing: request.billing.clone(), + shipping: request.shipping.clone(), + customer_id: request.customer_id.clone(), + customer_present: request.customer_present.clone(), + description: request.description.clone(), + return_url: request.return_url.clone(), + setup_future_usage: request.setup_future_usage, + apply_mit_exemption: request.apply_mit_exemption.clone(), + statement_descriptor: request.statement_descriptor.clone(), + order_details: request.order_details.clone(), + allowed_payment_method_types: request.allowed_payment_method_types.clone(), + metadata: request.metadata.clone(), + connector_metadata: request.connector_metadata.clone(), + feature_metadata: request.feature_metadata.clone(), + payment_link_enabled: request.payment_link_enabled.clone(), + payment_link_config: request.payment_link_config.clone(), + request_incremental_authorization: request.request_incremental_authorization, + session_expiry: request.session_expiry, + frm_metadata: request.frm_metadata.clone(), + request_external_three_ds_authentication: request + .request_external_three_ds_authentication + .clone(), + } + } +} + +#[cfg(feature = "v2")] +impl From<&PaymentsRequest> for PaymentsConfirmIntentRequest { + fn from(request: &PaymentsRequest) -> Self { + Self { + return_url: request.return_url.clone(), + payment_method_data: request.payment_method_data.clone(), + payment_method_type: request.payment_method_type, + payment_method_subtype: request.payment_method_subtype, + shipping: request.shipping.clone(), + customer_acceptance: request.customer_acceptance.clone(), + browser_info: request.browser_info.clone(), + } + } +} + +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, ToSchema)] +pub struct PaymentsResponse { + /// Unique identifier for the payment. This ensures idempotency for multiple payments + /// that have been done by a single merchant. + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + #[schema(value_type = IntentStatus, example = "success")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = String + )] + pub customer_id: Option, + + /// The connector used for the payment + #[schema(example = "stripe")] + pub connector: String, + + /// It's a token used for client side verification. + #[schema(value_type = String)] + pub client_secret: common_utils::types::ClientSecret, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method information provided for making a payment + #[schema(value_type = Option)] + #[serde(serialize_with = "serialize_payment_method_data_response")] + pub payment_method_data: Option, + + /// The payment method type for this payment attempt + #[schema(value_type = PaymentMethod, example = "wallet")] + pub payment_method_type: api_enums::PaymentMethod, + + #[schema(value_type = PaymentMethodType, example = "apple_pay")] + pub payment_method_subtype: api_enums::PaymentMethodType, + + /// Additional information required for redirection + pub next_action: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_transaction_id: Option, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// Connector token information that can be used to make payments directly by the merchant. + pub connector_token_details: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = String)] + pub merchant_connector_id: id_type::MerchantConnectorAccountId, + + /// The browser information used for this payment + #[schema(value_type = Option)] + pub browser_info: Option, + + /// Error details for the payment if any + pub error: Option, +} + // Serialize is implemented because, this will be serialized in the api events. // Usually request types should not have serialize implemented. // @@ -4885,6 +5151,9 @@ pub struct PaymentsConfirmIntentResponse { #[schema(value_type = Option, example = "993672945374576J")] pub connector_reference_id: Option, + /// Connector token information that can be used to make payments directly by the merchant. + pub connector_token_details: Option, + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment #[schema(value_type = String)] pub merchant_connector_id: id_type::MerchantConnectorAccountId, @@ -4897,6 +5166,15 @@ pub struct PaymentsConfirmIntentResponse { pub error: Option, } +/// Token information that can be used to initiate transactions by the merchant. +#[cfg(feature = "v2")] +#[derive(Debug, Serialize, ToSchema)] +pub struct ConnectorTokenDetails { + /// A token that can be used to make payments directly with the connector. + #[schema(example = "pm_9UhMqBMEOooRIvJFFdeW")] + pub token: String, +} + // TODO: have a separate response for detailed, summarized /// Response for Payment Intent Confirm #[cfg(feature = "v2")] @@ -5104,6 +5382,7 @@ pub struct PaymentListConstraints { pub created_gte: Option, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Serialize, ToSchema)] pub struct PaymentListResponse { /// The number of payments included in the list @@ -5130,6 +5409,7 @@ pub struct IncrementalAuthorizationResponse { pub previously_authorized_amount: MinorUnit, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Serialize)] pub struct PaymentListResponseV2 { /// The number of payments included in the list for given constraints diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index dbd867c9e55..a8fdf882e70 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -90,10 +90,10 @@ pub struct PaymentAttempt { pub payment_method_billing_address: Option, pub redirection_data: Option, pub connector_payment_data: Option, + pub connector_token_details: Option, pub id: id_type::GlobalAttemptId, pub shipping_cost: Option, pub order_tax_amount: Option, - pub connector_mandate_detail: Option, pub card_discovery: Option, } @@ -216,6 +216,26 @@ impl ConnectorTransactionIdTrait for PaymentAttempt { } } +#[cfg(feature = "v2")] +#[derive( + Clone, Debug, PartialEq, Eq, serde::Deserialize, serde::Serialize, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +pub struct ConnectorTokenDetails { + pub connector_mandate_id: Option, + pub connector_mandate_request_reference_id: Option, +} + +#[cfg(feature = "v2")] +common_utils::impl_to_sql_from_sql_json!(ConnectorTokenDetails); + +#[cfg(feature = "v2")] +impl ConnectorTokenDetails { + pub fn get_connector_mandate_request_reference_id(&self) -> Option { + self.connector_mandate_request_reference_id.clone() + } +} + #[derive(Clone, Debug, Eq, PartialEq, Queryable, Serialize, Deserialize)] pub struct PaymentListFilters { pub connector: Vec, @@ -279,7 +299,7 @@ pub struct PaymentAttemptNew { pub payment_method_type_v2: storage_enums::PaymentMethod, pub payment_method_subtype: storage_enums::PaymentMethodType, pub id: id_type::GlobalAttemptId, - pub connector_mandate_detail: Option, + pub connector_token_details: Option, pub card_discovery: Option, } @@ -796,6 +816,7 @@ pub struct PaymentAttemptUpdateInternal { // client_version: Option, // customer_acceptance: Option, // card_network: Option, + pub connector_token_details: Option, } #[cfg(feature = "v1")] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 07a76c13ae7..9b1c3aa7d45 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -872,11 +872,11 @@ diesel::table! { redirection_data -> Nullable, #[max_length = 512] connector_payment_data -> Nullable, + connector_token_details -> Nullable, #[max_length = 64] id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, - connector_mandate_detail -> Nullable, card_discovery -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/consts.rs b/crates/hyperswitch_domain_models/src/consts.rs index 3808e615c0a..4a87c35b5c1 100644 --- a/crates/hyperswitch_domain_models/src/consts.rs +++ b/crates/hyperswitch_domain_models/src/consts.rs @@ -5,3 +5,6 @@ pub const API_VERSION: common_enums::ApiVersion = common_enums::ApiVersion::V1; #[cfg(all(feature = "v2", feature = "customer_v2"))] pub const API_VERSION: common_enums::ApiVersion = common_enums::ApiVersion::V2; + +/// Length of the unique reference ID generated for connector mandate requests +pub const CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH: usize = 18; diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 74804e658a7..65455b3b193 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -33,9 +33,13 @@ use time::PrimitiveDateTime; #[cfg(all(feature = "v1", feature = "olap"))] use super::PaymentIntent; #[cfg(feature = "v2")] -use crate::type_encryption::{crypto_operation, CryptoOperation}; -#[cfg(feature = "v2")] -use crate::{address::Address, merchant_key_store::MerchantKeyStore, router_response_types}; +use crate::{ + address::Address, + consts, + merchant_key_store::MerchantKeyStore, + router_response_types, + type_encryption::{crypto_operation, CryptoOperation}, +}; use crate::{ behaviour, errors, mandates::{MandateDataType, MandateDetails}, @@ -400,8 +404,8 @@ pub struct PaymentAttempt { pub payment_method_billing_address: Option>, /// The global identifier for the payment attempt pub id: id_type::GlobalAttemptId, - /// The connector mandate details which are stored temporarily - pub connector_mandate_detail: Option, + /// Connector token information that can be used to make payments directly by the merchant. + pub connector_token_details: Option, /// Indicates the method by which a card is discovered during a payment pub card_discovery: Option, } @@ -520,7 +524,12 @@ impl PaymentAttempt { external_reference_id: None, payment_method_billing_address, error: None, - connector_mandate_detail: None, + connector_token_details: Some(diesel_models::ConnectorTokenDetails { + connector_mandate_id: None, + connector_mandate_request_reference_id: Some(common_utils::generate_id_with_len( + consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, + )), + }), id, card_discovery: None, }) @@ -1413,6 +1422,18 @@ impl PaymentAttemptUpdate { } } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, Serialize)] +pub struct ConfirmIntentResponseUpdate { + pub status: storage_enums::AttemptStatus, + pub connector_payment_id: Option, + pub updated_by: String, + pub redirection_data: Option, + pub connector_metadata: Option, + pub amount_capturable: Option, + pub connector_token_details: Option, +} + #[cfg(feature = "v2")] #[derive(Debug, Clone, Serialize)] pub enum PaymentAttemptUpdate { @@ -1424,14 +1445,7 @@ pub enum PaymentAttemptUpdate { merchant_connector_id: id_type::MerchantConnectorAccountId, }, /// Update the payment attempt on confirming the intent, after calling the connector on success response - ConfirmIntentResponse { - status: storage_enums::AttemptStatus, - connector_payment_id: Option, - updated_by: String, - redirection_data: Option, - connector_metadata: Option, - amount_capturable: Option, - }, + ConfirmIntentResponse(Box), /// Update the payment attempt after force syncing with the connector SyncUpdate { status: storage_enums::AttemptStatus, @@ -1791,7 +1805,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_id, payment_method_billing_address, connector, - connector_mandate_detail, + connector_token_details, card_discovery, } = self; @@ -1869,7 +1883,7 @@ impl behaviour::Conversion for PaymentAttempt { tax_on_surcharge, payment_method_billing_address: payment_method_billing_address.map(Encryption::from), connector_payment_data, - connector_mandate_detail, + connector_token_details, card_discovery, }) } @@ -1981,7 +1995,7 @@ impl behaviour::Conversion for PaymentAttempt { external_reference_id: storage_model.external_reference_id, connector: storage_model.connector, payment_method_billing_address, - connector_mandate_detail: storage_model.connector_mandate_detail, + connector_token_details: storage_model.connector_token_details, card_discovery: storage_model.card_discovery, }) } @@ -2066,7 +2080,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_subtype: self.payment_method_subtype, payment_method_type_v2: self.payment_method_type, id: self.id, - connector_mandate_detail: self.connector_mandate_detail, + connector_token_details: self.connector_token_details, card_discovery: self.card_discovery, }) } @@ -2098,6 +2112,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, amount_capturable: None, amount_to_capture: None, + connector_token_details: None, }, PaymentAttemptUpdate::ErrorUpdate { status, @@ -2122,33 +2137,39 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, amount_capturable, amount_to_capture: None, + connector_token_details: None, }, - PaymentAttemptUpdate::ConfirmIntentResponse { - status, - connector_payment_id, - updated_by, - redirection_data, - connector_metadata, - amount_capturable, - } => Self { - status: Some(status), - amount_capturable, - error_message: None, - error_code: None, - modified_at: common_utils::date_time::now(), - browser_info: None, - error_reason: None, - updated_by, - merchant_connector_id: None, - unified_code: None, - unified_message: None, - connector_payment_id, - connector: None, - redirection_data: redirection_data - .map(diesel_models::payment_attempt::RedirectForm::from), - connector_metadata, - amount_to_capture: None, - }, + PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => { + let ConfirmIntentResponseUpdate { + status, + connector_payment_id, + updated_by, + redirection_data, + connector_metadata, + amount_capturable, + connector_token_details, + } = *confirm_intent_response_update; + Self { + status: Some(status), + amount_capturable, + error_message: None, + error_code: None, + modified_at: common_utils::date_time::now(), + browser_info: None, + error_reason: None, + updated_by, + merchant_connector_id: None, + unified_code: None, + unified_message: None, + connector_payment_id, + connector: None, + redirection_data: redirection_data + .map(diesel_models::payment_attempt::RedirectForm::from), + connector_metadata, + amount_to_capture: None, + connector_token_details, + } + } PaymentAttemptUpdate::SyncUpdate { status, amount_capturable, @@ -2170,6 +2191,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal redirection_data: None, connector_metadata: None, amount_to_capture: None, + connector_token_details: None, }, PaymentAttemptUpdate::CaptureUpdate { status, @@ -2192,6 +2214,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector: None, redirection_data: None, connector_metadata: None, + connector_token_details: None, }, PaymentAttemptUpdate::PreCaptureUpdate { amount_to_capture, @@ -2213,6 +2236,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal status: None, connector_metadata: None, amount_capturable: None, + connector_token_details: None, }, } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 322b4aa4525..1ad7d2dcbdd 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -10,6 +10,14 @@ use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; use crate::{payment_address::PaymentAddress, payment_method_data, payments}; +#[cfg(feature = "v2")] +use crate::{ + payments::{ + payment_attempt::{ErrorDetails, PaymentAttemptUpdate}, + payment_intent::PaymentIntentUpdate, + }, + router_flow_types, router_request_types, router_response_types, +}; #[derive(Debug, Clone)] pub struct RouterData { @@ -416,15 +424,6 @@ impl ErrorResponse { } } -#[cfg(feature = "v2")] -use crate::{ - payments::{ - payment_attempt::{ErrorDetails, PaymentAttemptUpdate}, - payment_intent::PaymentIntentUpdate, - }, - router_flow_types, router_request_types, router_response_types, -}; - /// Get updatable trakcer objects of payment intent and payment attempt #[cfg(feature = "v2")] pub trait TrackerPostUpdateObjects { @@ -518,14 +517,27 @@ impl | router_request_types::ResponseId::EncodedData(id) => Some(id.to_owned()), }; - PaymentAttemptUpdate::ConfirmIntentResponse { - status: attempt_status, - connector_payment_id, - updated_by: storage_scheme.to_string(), - redirection_data: *redirection_data.clone(), - amount_capturable, - connector_metadata: connector_metadata.clone().map(Secret::new), - } + PaymentAttemptUpdate::ConfirmIntentResponse(Box::new( + payments::payment_attempt::ConfirmIntentResponseUpdate { + status: attempt_status, + connector_payment_id, + updated_by: storage_scheme.to_string(), + redirection_data: *redirection_data.clone(), + amount_capturable, + connector_metadata: connector_metadata.clone().map(Secret::new), + connector_token_details: response_router_data + .get_updated_connector_token_details( + payment_data + .payment_attempt + .connector_token_details + .as_ref() + .and_then(|token_details| { + token_details + .get_connector_mandate_request_reference_id() + }), + ), + }, + )) } router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => { todo!() @@ -1092,3 +1104,222 @@ impl } } } + +#[cfg(feature = "v2")] +impl + TrackerPostUpdateObjects< + router_flow_types::SetupMandate, + router_request_types::SetupMandateRequestData, + payments::PaymentConfirmData, + > + for RouterData< + router_flow_types::SetupMandate, + router_request_types::SetupMandateRequestData, + router_response_types::PaymentsResponseData, + > +{ + fn get_payment_intent_update( + &self, + payment_data: &payments::PaymentConfirmData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentIntentUpdate { + let amount_captured = self.get_captured_amount(payment_data); + match self.response { + Ok(ref _response) => PaymentIntentUpdate::ConfirmIntentPostUpdate { + status: common_enums::IntentStatus::from( + self.get_attempt_status_for_db_update(payment_data), + ), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + Err(ref error) => PaymentIntentUpdate::ConfirmIntentPostUpdate { + status: error + .attempt_status + .map(common_enums::IntentStatus::from) + .unwrap_or(common_enums::IntentStatus::Failed), + amount_captured, + updated_by: storage_scheme.to_string(), + }, + } + } + + fn get_payment_attempt_update( + &self, + payment_data: &payments::PaymentConfirmData, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> PaymentAttemptUpdate { + let amount_capturable = self.get_amount_capturable(payment_data); + + match self.response { + Ok(ref response_router_data) => match response_router_data { + router_response_types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data, + mandate_reference, + connector_metadata, + network_txn_id, + connector_response_reference_id, + incremental_authorization_allowed, + charge_id, + } => { + let attempt_status = self.get_attempt_status_for_db_update(payment_data); + + let connector_payment_id = match resource_id { + router_request_types::ResponseId::NoResponseId => None, + router_request_types::ResponseId::ConnectorTransactionId(id) + | router_request_types::ResponseId::EncodedData(id) => Some(id.to_owned()), + }; + + PaymentAttemptUpdate::ConfirmIntentResponse(Box::new( + payments::payment_attempt::ConfirmIntentResponseUpdate { + status: attempt_status, + connector_payment_id, + updated_by: storage_scheme.to_string(), + redirection_data: *redirection_data.clone(), + amount_capturable, + connector_metadata: connector_metadata.clone().map(Secret::new), + connector_token_details: response_router_data + .get_updated_connector_token_details( + payment_data + .payment_attempt + .connector_token_details + .as_ref() + .and_then(|token_details| { + token_details + .get_connector_mandate_request_reference_id() + }), + ), + }, + )) + } + router_response_types::PaymentsResponseData::MultipleCaptureResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::SessionTokenResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::TransactionUnresolvedResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::TokenizationResponse { .. } => todo!(), + router_response_types::PaymentsResponseData::ConnectorCustomerResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::ThreeDSEnrollmentResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PreProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::IncrementalAuthorizationResponse { + .. + } => todo!(), + router_response_types::PaymentsResponseData::PostProcessingResponse { .. } => { + todo!() + } + router_response_types::PaymentsResponseData::SessionUpdateResponse { .. } => { + todo!() + } + }, + Err(ref error_response) => { + let ErrorResponse { + code, + message, + reason, + status_code: _, + attempt_status, + connector_transaction_id, + } = error_response.clone(); + let attempt_status = attempt_status.unwrap_or(self.status); + + let error_details = ErrorDetails { + code, + message, + reason, + unified_code: None, + unified_message: None, + }; + + PaymentAttemptUpdate::ErrorUpdate { + status: attempt_status, + error: error_details, + amount_capturable, + connector_payment_id: connector_transaction_id, + updated_by: storage_scheme.to_string(), + } + } + } + } + + fn get_attempt_status_for_db_update( + &self, + _payment_data: &payments::PaymentConfirmData, + ) -> common_enums::AttemptStatus { + // For this step, consider whatever status was given by the connector module + // We do not need to check for amount captured or amount capturable here because we are authorizing the whole amount + self.status + } + + fn get_amount_capturable( + &self, + payment_data: &payments::PaymentConfirmData, + ) -> Option { + // Based on the status of the response, we can determine the amount capturable + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is already succeeded / failed we cannot capture any more amount + // So set the amount capturable to zero + common_enums::IntentStatus::Succeeded + | common_enums::IntentStatus::Failed + | common_enums::IntentStatus::Cancelled => Some(MinorUnit::zero()), + // For these statuses, update the capturable amount when it reaches terminal / capturable state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + // If status is requires capture, get the total amount that can be captured + // This is in cases where the capture method will be `manual` or `manual_multiple` + // We do not need to handle the case where amount_to_capture is provided here as it cannot be passed in authroize flow + common_enums::IntentStatus::RequiresCapture => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // Invalid statues for this flow, after doing authorization this state is invalid + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } + + fn get_captured_amount( + &self, + payment_data: &payments::PaymentConfirmData, + ) -> Option { + // Based on the status of the response, we can determine the amount that was captured + let intent_status = common_enums::IntentStatus::from(self.status); + match intent_status { + // If the status is succeeded then we have captured the whole amount + // we need not check for `amount_to_capture` here because passing `amount_to_capture` when authorizing is not supported + common_enums::IntentStatus::Succeeded => { + let total_amount = payment_data.payment_attempt.amount_details.get_net_amount(); + Some(total_amount) + } + // No amount is captured + common_enums::IntentStatus::Cancelled | common_enums::IntentStatus::Failed => { + Some(MinorUnit::zero()) + } + // For these statuses, update the amount captured when it reaches terminal state + common_enums::IntentStatus::RequiresCustomerAction + | common_enums::IntentStatus::RequiresMerchantAction + | common_enums::IntentStatus::Processing => None, + // Invalid states for this flow + common_enums::IntentStatus::RequiresPaymentMethod + | common_enums::IntentStatus::RequiresConfirmation => None, + // No amount has been captured yet + common_enums::IntentStatus::RequiresCapture => Some(MinorUnit::zero()), + // Invalid statues for this flow + common_enums::IntentStatus::PartiallyCaptured + | common_enums::IntentStatus::PartiallyCapturedAndCapturable => None, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 1a1488031f5..4d15d715ff7 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -149,6 +149,7 @@ impl PaymentsResponseData { .into()), } } + pub fn merge_transaction_responses( auth_response: &Self, capture_response: &Self, @@ -206,6 +207,31 @@ impl PaymentsResponseData { .into()), } } + + #[cfg(feature = "v2")] + pub fn get_updated_connector_token_details( + &self, + original_connector_mandate_request_reference_id: Option, + ) -> Option { + if let Self::TransactionResponse { + mandate_reference, .. + } = self + { + mandate_reference.clone().map(|mandate_ref| { + let connector_mandate_id = mandate_ref.connector_mandate_id; + let connector_mandate_request_reference_id = mandate_ref + .connector_mandate_request_reference_id + .or(original_connector_mandate_request_reference_id); + + diesel_models::ConnectorTokenDetails { + connector_mandate_id, + connector_mandate_request_reference_id, + } + }) + } else { + None + } + } } #[derive(Debug, Clone)] diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 701fb22d1ad..1078d8080ca 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -124,6 +124,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_update_intent, routes::payments::payments_confirm_intent, routes::payments::payment_status, + routes::payments::payments_create_and_confirm_intent, routes::payments::payments_connector_session, routes::payments::list_payment_methods, @@ -359,8 +360,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::CardRedirectData, api_models::payments::CardToken, api_models::payments::CustomerAcceptance, + api_models::payments::ConnectorTokenDetails, + api_models::payments::PaymentsRequest, api_models::payments::PaymentsResponse, - api_models::payments::PaymentsCreateResponseOpenApi, api_models::payments::PaymentRetrieveBody, api_models::payments::PaymentsRetrieveRequest, api_models::payments::PaymentsCaptureRequest, @@ -424,7 +426,6 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SamsungPayTokenData, api_models::payments::PaymentsCancelRequest, api_models::payments::PaymentListConstraints, - api_models::payments::PaymentListResponse, api_models::payments::CashappQr, api_models::payments::BankTransferData, api_models::payments::BankTransferNextStepsData, diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs index afc9f4a1735..4c0c201e290 100644 --- a/crates/openapi/src/routes/payment_method.rs +++ b/crates/openapi/src/routes/payment_method.rs @@ -334,7 +334,7 @@ pub async fn payment_method_delete_api() {} ( "X-Profile-Id" = String, Header, description = "Profile ID associated to the payment method intent", - example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + example = "pro_abcdefghijklmnop" ), ), responses( diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index 33bb95618e8..ceccd53c4dd 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -659,7 +659,7 @@ pub fn payments_get_intent() {} ( "X-Profile-Id" = String, Header, description = "Profile ID associated to the payment intent", - example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + example = "pro_abcdefghijklmnop" ), ), request_body( @@ -695,7 +695,7 @@ pub fn payments_update_intent() {} ( "X-Profile-Id" = String, Header, description = "Profile ID associated to the payment intent", - example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + example = "pro_abcdefghijklmnop" ), ( "X-Client-Secret" = String, Header, @@ -710,6 +710,7 @@ pub fn payments_update_intent() {} "Confirm the payment intent with card details" = ( value = json!({ "payment_method_type": "card", + "payment_method_subtype": "credit", "payment_method_data": { "card": { "card_number": "4242424242424242", @@ -756,6 +757,57 @@ pub fn payments_confirm_intent() {} #[cfg(feature = "v2")] pub fn payment_status() {} +/// Payments - Create and Confirm Intent +/// +/// **Creates and confirms a payment intent object when the amount and payment method information are passed.** +/// +/// You will require the 'API - Key' from the Hyperswitch dashboard to make the call. +#[utoipa::path( + post, + path = "/v2/payments", + params ( + ( + "X-Profile-Id" = String, Header, + description = "Profile ID associated to the payment intent", + example = "pro_abcdefghijklmnop" + ) + ), + request_body( + content = PaymentsRequest, + examples( + ( + "Create and confirm the payment intent with amount and card details" = ( + value = json!({ + "amount_details": { + "order_amount": 6540, + "currency": "USD" + }, + "payment_method_type": "card", + "payment_method_subtype": "credit", + "payment_method_data": { + "card": { + "card_number": "4242424242424242", + "card_exp_month": "10", + "card_exp_year": "25", + "card_holder_name": "joseph Doe", + "card_cvc": "123" + } + }, + }) + ) + ), + ), + ), + responses( + (status = 200, description = "Payment created", body = PaymentsResponse), + (status = 400, description = "Missing Mandatory fields") + ), + tag = "Payments", + operation_id = "Create and Confirm Payment Intent", + security(("api_key" = [])), +)] +pub fn payments_create_and_confirm_intent() {} + #[derive(utoipa::ToSchema)] #[schema(rename_all = "lowercase")] pub(crate) enum ForceSync { @@ -777,7 +829,7 @@ pub(crate) enum ForceSync { ( "X-Profile-Id" = String, Header, description = "Profile ID associated to the payment intent", - example = json!({"X-Profile-Id": "pro_abcdefghijklmnop"}) + example = "pro_abcdefghijklmnop" ), ( "X-Client-Secret" = String, Header, diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 9e00bcd6bce..585e894adf9 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -6,7 +6,9 @@ pub mod user_role; use std::collections::HashSet; use common_utils::consts; +pub use hyperswitch_domain_models::consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH; pub use hyperswitch_interfaces::consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}; + // ID generation pub(crate) const ID_LENGTH: usize = 20; pub(crate) const MAX_ID_LENGTH: usize = 64; @@ -136,9 +138,6 @@ pub const DEFAULT_UNIFIED_ERROR_MESSAGE: &str = "Something went wrong"; // Recon's feature tag pub const RECON_FEATURE_TAG: &str = "RECONCILIATION AND SETTLEMENT"; -// Length of the unique reference ID generated for connector mandate requests -pub const CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH: usize = 18; - /// Default allowed domains for payment links pub const DEFAULT_ALLOWED_DOMAINS: Option> = None; diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 8e31ab7975f..47d2024834e 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1610,6 +1610,192 @@ where ) } +#[allow(clippy::too_many_arguments)] +#[cfg(feature = "v2")] +pub(crate) async fn payments_create_and_confirm_intent( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + request: payments_api::PaymentsRequest, + payment_id: id_type::GlobalPaymentId, + mut header_payload: HeaderPayload, + platform_merchant_account: Option, +) -> RouterResponse { + use actix_http::body::MessageBody; + use common_utils::ext_traits::BytesExt; + use hyperswitch_domain_models::{ + payments::{PaymentConfirmData, PaymentIntentData}, + router_flow_types::{Authorize, PaymentCreateIntent, SetupMandate}, + }; + + let payload = payments_api::PaymentsCreateIntentRequest::from(&request); + + let create_intent_response = Box::pin(payments_intent_core::< + PaymentCreateIntent, + payments_api::PaymentsIntentResponse, + _, + _, + PaymentIntentData, + >( + state.clone(), + req_state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + operations::PaymentIntentCreate, + payload, + payment_id.clone(), + header_payload.clone(), + platform_merchant_account, + )) + .await?; + + logger::info!(?create_intent_response); + let create_intent_response = handle_payments_intent_response(create_intent_response)?; + + // Adding client secret to ensure client secret validation passes during confirm intent step + header_payload.client_secret = Some(create_intent_response.client_secret.clone()); + + let payload = payments_api::PaymentsConfirmIntentRequest::from(&request); + + let confirm_intent_response = decide_authorize_or_setup_intent_flow( + state, + req_state, + merchant_account, + profile, + key_store, + &create_intent_response, + payload, + payment_id, + header_payload, + ) + .await?; + + logger::info!(?confirm_intent_response); + let confirm_intent_response = handle_payments_intent_response(confirm_intent_response)?; + + construct_payments_response(create_intent_response, confirm_intent_response) +} + +#[cfg(feature = "v2")] +#[inline] +fn handle_payments_intent_response( + response: hyperswitch_domain_models::api::ApplicationResponse, +) -> CustomResult { + match response { + hyperswitch_domain_models::api::ApplicationResponse::Json(body) + | hyperswitch_domain_models::api::ApplicationResponse::JsonWithHeaders((body, _)) => { + Ok(body) + } + hyperswitch_domain_models::api::ApplicationResponse::StatusOk + | hyperswitch_domain_models::api::ApplicationResponse::TextPlain(_) + | hyperswitch_domain_models::api::ApplicationResponse::JsonForRedirection(_) + | hyperswitch_domain_models::api::ApplicationResponse::Form(_) + | hyperswitch_domain_models::api::ApplicationResponse::PaymentLinkForm(_) + | hyperswitch_domain_models::api::ApplicationResponse::FileData(_) + | hyperswitch_domain_models::api::ApplicationResponse::GenericLinkForm(_) => { + Err(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unexpected response from payment intent core") + } + } +} + +#[cfg(feature = "v2")] +#[inline] +fn construct_payments_response( + create_intent_response: payments_api::PaymentsIntentResponse, + confirm_intent_response: payments_api::PaymentsConfirmIntentResponse, +) -> RouterResponse { + let response = payments_api::PaymentsResponse { + id: confirm_intent_response.id, + status: confirm_intent_response.status, + amount: confirm_intent_response.amount, + customer_id: confirm_intent_response.customer_id, + connector: confirm_intent_response.connector, + client_secret: confirm_intent_response.client_secret, + created: confirm_intent_response.created, + payment_method_data: confirm_intent_response.payment_method_data, + payment_method_type: confirm_intent_response.payment_method_type, + payment_method_subtype: confirm_intent_response.payment_method_subtype, + next_action: confirm_intent_response.next_action, + connector_transaction_id: confirm_intent_response.connector_transaction_id, + connector_reference_id: confirm_intent_response.connector_reference_id, + connector_token_details: confirm_intent_response.connector_token_details, + merchant_connector_id: confirm_intent_response.merchant_connector_id, + browser_info: confirm_intent_response.browser_info, + error: confirm_intent_response.error, + }; + + Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( + response, + )) +} + +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +async fn decide_authorize_or_setup_intent_flow( + state: SessionState, + req_state: ReqState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + create_intent_response: &payments_api::PaymentsIntentResponse, + confirm_intent_request: payments_api::PaymentsConfirmIntentRequest, + payment_id: id_type::GlobalPaymentId, + header_payload: HeaderPayload, +) -> RouterResponse { + use hyperswitch_domain_models::{ + payments::PaymentConfirmData, + router_flow_types::{Authorize, SetupMandate}, + }; + + if create_intent_response.amount_details.order_amount == MinorUnit::zero() { + Box::pin(payments_core::< + SetupMandate, + api_models::payments::PaymentsConfirmIntentResponse, + _, + _, + _, + PaymentConfirmData, + >( + state, + req_state, + merchant_account, + profile, + key_store, + operations::PaymentIntentConfirm, + confirm_intent_request, + payment_id, + CallConnectorAction::Trigger, + header_payload, + )) + .await + } else { + Box::pin(payments_core::< + Authorize, + api_models::payments::PaymentsConfirmIntentResponse, + _, + _, + _, + PaymentConfirmData, + >( + state, + req_state, + merchant_account, + profile, + key_store, + operations::PaymentIntentConfirm, + confirm_intent_request, + payment_id, + CallConnectorAction::Trigger, + header_payload, + )) + .await + } +} + fn is_start_pay(operation: &Op) -> bool { format!("{operation:?}").eq("PaymentStart") } 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 ee8ff78bdab..52014730eea 100644 --- a/crates/router/src/core/payments/flows/setup_mandate_flow.rs +++ b/crates/router/src/core/payments/flows/setup_mandate_flow.rs @@ -15,6 +15,7 @@ use crate::{ types::{self, api, domain}, }; +#[cfg(feature = "v1")] #[async_trait] impl ConstructFlowSpecificData< @@ -23,7 +24,6 @@ impl types::PaymentsResponseData, > for PaymentData { - #[cfg(feature = "v1")] async fn construct_router_data<'a>( &self, state: &SessionState, @@ -52,7 +52,27 @@ impl .await } - #[cfg(feature = "v2")] + async fn get_merchant_recipient_data<'a>( + &self, + _state: &SessionState, + _merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + _merchant_connector_account: &helpers::MerchantConnectorAccountType, + _connector: &api::ConnectorData, + ) -> RouterResult> { + Ok(None) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl + ConstructFlowSpecificData< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for hyperswitch_domain_models::payments::PaymentConfirmData +{ async fn construct_router_data<'a>( &self, state: &SessionState, @@ -64,7 +84,20 @@ impl merchant_recipient_data: Option, header_payload: Option, ) -> RouterResult { - todo!() + Box::pin( + transformers::construct_payment_router_data_for_setup_mandate( + state, + self.clone(), + connector_id, + merchant_account, + key_store, + customer, + merchant_connector_account, + merchant_recipient_data, + header_payload, + ), + ) + .await } async fn get_merchant_recipient_data<'a>( diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 09fb3714421..b0df22f3d04 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3163,6 +3163,7 @@ pub async fn delete_ephemeral_key( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(feature = "v1")] pub fn make_pg_redirect_response( payment_id: id_type::PaymentId, response: &api::PaymentsResponse, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 9b1c2ea3be3..5f3b515c96e 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -777,7 +777,7 @@ impl GetTracker, api::PaymentsRequest> None, // update_history None, // mandate_metadata Some(common_utils::generate_id_with_len( - consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH.to_owned(), + consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), // connector_mandate_request_reference_id )), ); diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 6ace8c3018f..dd72bfff307 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -560,7 +560,7 @@ impl GetTracker, api::PaymentsRequest> None, // update_history None, // mandate_metadata Some(common_utils::generate_id_with_len( - consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH.to_owned(), + consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), // connector_mandate_request_reference_id ), )); diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 07342ffb4e6..748b71469ba 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -2423,6 +2423,95 @@ impl PostUpdateTracker, types::PaymentsSyncDat } } +#[cfg(feature = "v2")] +impl Operation for &PaymentResponse { + type Data = PaymentConfirmData; + fn to_post_update_tracker( + &self, + ) -> RouterResult< + &(dyn PostUpdateTracker + Send + Sync), + > { + Ok(*self) + } +} + +#[cfg(feature = "v2")] +impl Operation for PaymentResponse { + type Data = PaymentConfirmData; + fn to_post_update_tracker( + &self, + ) -> RouterResult< + &(dyn PostUpdateTracker + Send + Sync), + > { + Ok(self) + } +} + +#[cfg(feature = "v2")] +#[async_trait] +impl PostUpdateTracker, types::SetupMandateRequestData> + for PaymentResponse +{ + async fn update_tracker<'b>( + &'b self, + state: &'b SessionState, + mut payment_data: PaymentConfirmData, + response: types::RouterData, + key_store: &domain::MerchantKeyStore, + storage_scheme: enums::MerchantStorageScheme, + ) -> RouterResult> + where + F: 'b + Send + Sync, + types::RouterData: + hyperswitch_domain_models::router_data::TrackerPostUpdateObjects< + F, + types::SetupMandateRequestData, + PaymentConfirmData, + >, + { + use hyperswitch_domain_models::router_data::TrackerPostUpdateObjects; + + let db = &*state.store; + let key_manager_state = &state.into(); + + let response_router_data = response; + + let payment_intent_update = + response_router_data.get_payment_intent_update(&payment_data, storage_scheme); + let payment_attempt_update = + response_router_data.get_payment_attempt_update(&payment_data, storage_scheme); + + let updated_payment_intent = db + .update_payment_intent( + key_manager_state, + payment_data.payment_intent, + payment_intent_update, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment intent")?; + + let updated_payment_attempt = db + .update_payment_attempt( + key_manager_state, + key_store, + payment_data.payment_attempt, + payment_attempt_update, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to update payment attempt")?; + + payment_data.payment_intent = updated_payment_intent; + payment_data.payment_attempt = updated_payment_attempt; + + Ok(payment_data) + } +} + #[cfg(feature = "v1")] fn update_connector_mandate_details_for_the_flow( connector_mandate_id: Option, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 80c10a294ec..1e9adb0a06b 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -305,7 +305,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( }; let connector_mandate_request_reference_id = payment_data .payment_attempt - .connector_mandate_detail + .connector_token_details .as_ref() .and_then(|detail| detail.get_connector_mandate_request_reference_id()); @@ -428,7 +428,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( let connector_mandate_request_reference_id = payment_data .payment_attempt - .connector_mandate_detail + .connector_token_details .as_ref() .and_then(|detail| detail.get_connector_mandate_request_reference_id()); @@ -831,6 +831,198 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( Ok(router_data) } +#[cfg(feature = "v2")] +#[instrument(skip_all)] +#[allow(clippy::too_many_arguments)] +pub async fn construct_payment_router_data_for_setup_mandate<'a>( + state: &'a SessionState, + payment_data: hyperswitch_domain_models::payments::PaymentConfirmData, + connector_id: &str, + merchant_account: &domain::MerchantAccount, + _key_store: &domain::MerchantKeyStore, + customer: &'a Option, + merchant_connector_account: &domain::MerchantConnectorAccount, + _merchant_recipient_data: Option, + header_payload: Option, +) -> RouterResult { + fp_utils::when(merchant_connector_account.is_disabled(), || { + Err(errors::ApiErrorResponse::MerchantConnectorAccountDisabled) + })?; + + let auth_type = merchant_connector_account + .get_connector_account_details() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while parsing value for ConnectorAuthType")?; + + // TODO: Take Globalid and convert to connector reference id + let customer_id = customer + .to_owned() + .map(|customer| common_utils::id_type::CustomerId::try_from(customer.id.clone())) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Invalid global customer generated, not able to convert to reference id", + )?; + + let payment_method = payment_data.payment_attempt.payment_method_type; + + let router_base_url = &state.base_url; + let attempt = &payment_data.payment_attempt; + + let complete_authorize_url = Some(helpers::create_complete_authorize_url( + router_base_url, + attempt, + connector_id, + )); + + let webhook_url = Some(helpers::create_webhook_url( + router_base_url, + &attempt.merchant_id, + merchant_connector_account.get_id().get_string_repr(), + )); + + let router_return_url = payment_data + .payment_intent + .create_finish_redirection_url(router_base_url, &merchant_account.publishable_key) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to construct finish redirection url")? + .to_string(); + + let connector_request_reference_id = payment_data + .payment_intent + .merchant_reference_id + .map(|id| id.get_string_repr().to_owned()) + .unwrap_or(payment_data.payment_attempt.id.get_string_repr().to_owned()); + + let email = customer + .as_ref() + .and_then(|customer| customer.email.clone()) + .map(pii::Email::from); + + let browser_info = payment_data + .payment_attempt + .browser_info + .clone() + .map(types::BrowserInformation::from); + + // TODO: few fields are repeated in both routerdata and request + let request = types::SetupMandateRequestData { + currency: payment_data.payment_intent.amount_details.currency, + payment_method_data: payment_data + .payment_method_data + .get_required_value("payment_method_data")?, + amount: Some( + payment_data + .payment_attempt + .amount_details + .get_net_amount() + .get_amount_as_i64(), + ), + confirm: true, + statement_descriptor_suffix: None, + customer_acceptance: None, + mandate_id: None, + setup_future_usage: Some(payment_data.payment_intent.setup_future_usage), + off_session: None, + setup_mandate_details: None, + router_return_url: Some(router_return_url.clone()), + webhook_url, + browser_info, + email, + customer_name: None, + return_url: Some(router_return_url), + payment_method_type: Some(payment_data.payment_attempt.payment_method_subtype), + request_incremental_authorization: matches!( + payment_data + .payment_intent + .request_incremental_authorization, + RequestIncrementalAuthorization::True | RequestIncrementalAuthorization::Default + ), + metadata: payment_data.payment_intent.metadata, + minor_amount: Some(payment_data.payment_attempt.amount_details.get_net_amount()), + shipping_cost: payment_data.payment_intent.amount_details.shipping_cost, + }; + let connector_mandate_request_reference_id = payment_data + .payment_attempt + .connector_token_details + .as_ref() + .and_then(|detail| detail.get_connector_mandate_request_reference_id()); + + // TODO: evaluate the fields in router data, if they are required or not + let router_data = types::RouterData { + flow: PhantomData, + merchant_id: merchant_account.get_id().clone(), + tenant_id: state.tenant.tenant_id.clone(), + // TODO: evaluate why we need customer id at the connector level. We already have connector customer id. + customer_id, + connector: connector_id.to_owned(), + // TODO: evaluate why we need payment id at the connector level. We already have connector reference id + payment_id: payment_data + .payment_attempt + .payment_id + .get_string_repr() + .to_owned(), + // TODO: evaluate why we need attempt id at the connector level. We already have connector reference id + attempt_id: payment_data + .payment_attempt + .get_id() + .get_string_repr() + .to_owned(), + status: payment_data.payment_attempt.status, + payment_method, + connector_auth_type: auth_type, + description: payment_data + .payment_intent + .description + .as_ref() + .map(|description| description.get_string_repr()) + .map(ToOwned::to_owned), + // TODO: Create unified address + address: payment_data.payment_address.clone(), + auth_type: payment_data.payment_attempt.authentication_type, + connector_meta_data: None, + connector_wallets_details: None, + request, + response: Err(hyperswitch_domain_models::router_data::ErrorResponse::default()), + amount_captured: None, + minor_amount_captured: None, + access_token: None, + session_token: None, + reference_id: None, + payment_method_status: None, + payment_method_token: None, + connector_customer: None, + recurring_mandate_payment_data: None, + // TODO: This has to be generated as the reference id based on the connector configuration + // Some connectros might not accept accept the global id. This has to be done when generating the reference id + connector_request_reference_id, + preprocessing_id: payment_data.payment_attempt.preprocessing_step_id, + #[cfg(feature = "payouts")] + payout_method_data: None, + #[cfg(feature = "payouts")] + quote_id: None, + // TODO: take this based on the env + test_mode: Some(true), + payment_method_balance: None, + connector_api_version: None, + connector_http_status_code: None, + external_latency: None, + apple_pay_flow: None, + frm_metadata: None, + refund_id: None, + dispute_id: None, + connector_response: None, + integrity_check: Ok(()), + additional_merchant_data: None, + header_payload, + connector_mandate_request_reference_id, + authentication_id: None, + psd2_sca_exemption_type: None, + }; + + Ok(router_data) +} + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] @@ -1433,6 +1625,10 @@ where .as_ref() .map(|_| api_models::payments::NextActionData::RedirectToUrl { redirect_to_url }); + let connector_token_details = payment_attempt + .connector_token_details + .and_then(Option::::foreign_from); + let response = api_models::payments::PaymentsConfirmIntentResponse { id: payment_intent.id.clone(), status: payment_intent.status, @@ -1447,6 +1643,7 @@ where next_action, connector_transaction_id: payment_attempt.connector_payment_id.clone(), connector_reference_id: None, + connector_token_details, merchant_connector_id, browser_info: None, error, @@ -4146,6 +4343,7 @@ impl ForeignFrom for ConnectorMandateReferenc ) } } + impl ForeignFrom for DieselConnectorMandateReferenceId { fn foreign_from(value: ConnectorMandateReferenceId) -> Self { Self { @@ -4158,6 +4356,17 @@ impl ForeignFrom for DieselConnectorMandateReferenc } } +#[cfg(feature = "v2")] +impl ForeignFrom + for Option +{ + fn foreign_from(value: diesel_models::ConnectorTokenDetails) -> Self { + value + .connector_mandate_id + .map(|mandate_id| api_models::payments::ConnectorTokenDetails { token: mandate_id }) + } +} + impl ForeignFrom<(Self, Option<&api_models::payments::AdditionalPaymentData>)> for Option { diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 22bc38b3d6c..71cc3dceebb 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -1419,6 +1419,7 @@ pub fn get_external_authentication_request_poll_id( payment_id.get_external_authentication_request_poll_id() } +#[cfg(feature = "v1")] pub fn get_html_redirect_response_for_external_authentication( return_url_with_query_params: String, payment_response: &api_models::payments::PaymentsResponse, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index fffb3eab09e..17ab056e856 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -551,9 +551,15 @@ pub struct Payments; impl Payments { pub fn server(state: AppState) -> Scope { let mut route = web::scope("/v2/payments").app_data(web::Data::new(state)); - route = route.service( - web::resource("/create-intent").route(web::post().to(payments::payments_create_intent)), - ); + route = route + .service( + web::resource("") + .route(web::post().to(payments::payments_create_and_confirm_intent)), + ) + .service( + web::resource("/create-intent") + .route(web::post().to(payments::payments_create_intent)), + ); route = route.service( web::scope("/{payment_id}") diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 6550778619a..895ba4b8aff 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -146,6 +146,7 @@ impl From for ApiIdentifier { | Flow::PaymentsGetIntent | Flow::PaymentsPostSessionTokens | Flow::PaymentsUpdateIntent + | Flow::PaymentsCreateAndConfirmIntent | Flow::PaymentStartRedirection => Self::Payments, Flow::PayoutsCreate diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index c88f61f7c82..d461599b6f0 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -217,6 +217,57 @@ pub async fn payments_get_intent( .await } +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentsCreateAndConfirmIntent, payment_id))] +pub async fn payments_create_and_confirm_intent( + state: web::Data, + req: actix_web::HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let flow = Flow::PaymentsCreateAndConfirmIntent; + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + let global_payment_id = + common_utils::id_type::GlobalPaymentId::generate(&state.conf.cell_information.id); + + Box::pin(api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationData, request, req_state| { + payments::payments_create_and_confirm_intent( + state, + req_state, + auth.merchant_account, + auth.profile, + auth.key_store, + request, + global_payment_id.clone(), + header_payload.clone(), + auth.platform_merchant_account, + ) + }, + match env::which() { + env::Env::Production => &auth::HeaderAuth(auth::ApiKeyAuth), + _ => auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::ProfilePaymentWrite, + }, + req.headers(), + ), + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "v2")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsUpdateIntent, payment_id))] pub async fn payments_update_intent( diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index 0a56ff83f98..8095907eca8 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -592,7 +592,6 @@ async fn handle_response( match status_code { 200..=202 | 302 | 204 => { - logger::debug!(response=?response); // If needed add log line // logger:: error!( error_parsing_response=?err); let response = response diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index c11d54fc298..4ad2bd4998e 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -543,6 +543,7 @@ pub struct RedirectPaymentFlowResponse { pub profile: domain::Profile, } +#[cfg(feature = "v1")] #[derive(Clone, Debug)] pub struct AuthenticatePaymentFlowResponse { pub payments_response: api_models::payments::PaymentsResponse, diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 6ec0fe701e9..58159fc5688 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -1,8 +1,9 @@ #[cfg(feature = "v1")] -pub use api_models::payments::PaymentsRequest; +pub use api_models::payments::{PaymentListResponse, PaymentListResponseV2}; #[cfg(feature = "v2")] pub use api_models::payments::{ - PaymentsCreateIntentRequest, PaymentsIntentResponse, PaymentsUpdateIntentRequest, + PaymentsConfirmIntentRequest, PaymentsCreateIntentRequest, PaymentsIntentResponse, + PaymentsUpdateIntentRequest, }; pub use api_models::{ feature_matrix::{ @@ -14,18 +15,18 @@ pub use api_models::{ MandateTransactionType, MandateType, MandateValidationFields, NextActionType, OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, - PaymentListFiltersV2, PaymentListResponse, PaymentListResponseV2, PaymentMethodData, - PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, + PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, + PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRedirectRequest, PaymentsRedirectionResponse, - PaymentsRejectRequest, PaymentsResponse, PaymentsResponseForm, PaymentsRetrieveRequest, - PaymentsSessionRequest, PaymentsSessionResponse, PaymentsStartRequest, PgRedirectResponse, - PhoneDetails, RedirectionResponse, SessionToken, UrlDetails, VerifyRequest, VerifyResponse, - WalletData, + PaymentsRejectRequest, PaymentsRequest, PaymentsResponse, PaymentsResponseForm, + PaymentsRetrieveRequest, PaymentsSessionRequest, PaymentsSessionResponse, + PaymentsStartRequest, PgRedirectResponse, PhoneDetails, RedirectionResponse, SessionToken, + UrlDetails, VerifyRequest, VerifyResponse, WalletData, }, }; use error_stack::ResultExt; diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 935efa3c78b..a267d24af1d 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -172,6 +172,10 @@ pub enum Flow { PaymentsGetIntent, /// Payments Update Intent flow PaymentsUpdateIntent, + /// Payments confirm intent flow + PaymentsConfirmIntent, + /// Payments create and confirm intent flow + PaymentsCreateAndConfirmIntent, #[cfg(feature = "payouts")] /// Payouts create flow PayoutsCreate, @@ -525,8 +529,6 @@ pub enum Flow { PaymentsManualUpdate, /// Dynamic Tax Calcultion SessionUpdateTaxCalculation, - /// Payments confirm intent - PaymentsConfirmIntent, /// Payments post session tokens flow PaymentsPostSessionTokens, /// Payments start redirection flow diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql index a4616016abf..08e4b0a3568 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/down.sql @@ -42,7 +42,8 @@ ALTER TABLE payment_attempt DROP COLUMN payment_method_type_v2, DROP COLUMN tax_on_surcharge, DROP COLUMN payment_method_billing_address, DROP COLUMN redirection_data, - DROP COLUMN connector_payment_data; + DROP COLUMN connector_payment_data, + DROP COLUMN connector_token_details; ALTER TABLE merchant_connector_account ALTER COLUMN payment_methods_enabled TYPE JSON [ ]; diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql index faebb36cdf9..3926a02afa6 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql @@ -51,7 +51,8 @@ ADD COLUMN payment_method_type_v2 VARCHAR, ADD COLUMN tax_on_surcharge BIGINT, ADD COLUMN payment_method_billing_address BYTEA, ADD COLUMN redirection_data JSONB, - ADD COLUMN connector_payment_data VARCHAR(512); + ADD COLUMN connector_payment_data VARCHAR(512), + ADD COLUMN connector_token_details JSONB; -- Change the type of the column from JSON to JSONB ALTER TABLE merchant_connector_account diff --git a/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql b/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql similarity index 97% rename from v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql rename to v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql index 64cbd2233ea..2dcd3a16a6c 100644 --- a/v2_migrations/2024-10-08-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql @@ -88,7 +88,8 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN straight_through_algorithm JSONB, ADD COLUMN confirm BOOLEAN, ADD COLUMN authentication_data JSONB, - ADD COLUMN payment_method_billing_address_id VARCHAR(64); + ADD COLUMN payment_method_billing_address_id VARCHAR(64), + ADD COLUMN connector_mandate_detail JSONB; -- Create the index which was dropped because of dropping the column CREATE INDEX payment_attempt_connector_transaction_id_merchant_id_index ON payment_attempt (connector_transaction_id, merchant_id); diff --git a/v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql b/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql similarity index 97% rename from v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql rename to v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql index 276bea10bd5..65eb29c474f 100644 --- a/v2_migrations/2024-10-08-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql @@ -86,4 +86,5 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN straight_through_algorithm, DROP COLUMN confirm, DROP COLUMN authentication_data, - DROP COLUMN payment_method_billing_address_id; + DROP COLUMN payment_method_billing_address_id, + DROP COLUMN connector_mandate_detail; From 190095977819efac42da5483bfdae6420a7a402c Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:07:46 +0530 Subject: [PATCH 039/133] feat(core): Implement 3ds decision manger for V2 (#7022) --- .github/CODEOWNERS | 1 + Cargo.lock | 3 + crates/api_models/src/conditional_configs.rs | 96 ++++--------------- crates/common_types/Cargo.toml | 2 + crates/common_types/src/payments.rs | 89 +++++++++++++---- crates/diesel_models/src/business_profile.rs | 6 ++ crates/diesel_models/src/schema_v2.rs | 1 + crates/euclid/src/dssa/types.rs | 24 ++++- crates/euclid_wasm/Cargo.toml | 1 + crates/euclid_wasm/src/lib.rs | 4 +- .../src/business_profile.rs | 60 ++++++++++++ crates/router/src/core/admin.rs | 2 + crates/router/src/core/conditional_config.rs | 85 ++++++++++++++-- crates/router/src/core/payments.rs | 4 +- .../src/core/payments/conditional_configs.rs | 15 +-- .../conditional_configs/transformers.rs | 22 ----- crates/router/src/core/routing/helpers.rs | 11 --- crates/router/src/routes/app.rs | 7 +- crates/router/src/routes/routing.rs | 74 +++++++++++++- .../src/services/authorization/permissions.rs | 2 +- .../down.sql | 3 + .../up.sql | 3 + 22 files changed, 362 insertions(+), 153 deletions(-) delete mode 100644 crates/router/src/core/payments/conditional_configs/transformers.rs create mode 100644 v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql create mode 100644 v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dc4010d0fd0..6ad86dd4ccc 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -18,6 +18,7 @@ crates/router/src/services/ @juspay/hyperswitch-framework crates/router/src/db/ @juspay/hyperswitch-framework crates/router/src/routes/ @juspay/hyperswitch-framework migrations/ @juspay/hyperswitch-framework +v2_migrations/ @juspay/hyperswitch-framework api-reference/ @juspay/hyperswitch-framework api-reference-v2/ @juspay/hyperswitch-framework Cargo.toml @juspay/hyperswitch-framework diff --git a/Cargo.lock b/Cargo.lock index 4737fff7aa2..9e39e50ff52 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2039,8 +2039,10 @@ dependencies = [ "common_enums", "common_utils", "diesel", + "euclid", "serde", "serde_json", + "strum 0.26.3", "utoipa", ] @@ -3067,6 +3069,7 @@ version = "0.1.0" dependencies = [ "api_models", "common_enums", + "common_types", "connector_configs", "currency_conversion", "euclid", diff --git a/crates/api_models/src/conditional_configs.rs b/crates/api_models/src/conditional_configs.rs index 3aed34e47a7..ac7170bbdff 100644 --- a/crates/api_models/src/conditional_configs.rs +++ b/crates/api_models/src/conditional_configs.rs @@ -1,82 +1,10 @@ use common_utils::events; -use euclid::{ - dssa::types::EuclidAnalysable, - enums, - frontend::{ - ast::Program, - dir::{DirKeyKind, DirValue, EuclidDirFilter}, - }, - types::Metadata, -}; -use serde::{Deserialize, Serialize}; - -#[derive( - Clone, - Debug, - Hash, - PartialEq, - Eq, - strum::Display, - strum::VariantNames, - strum::EnumIter, - strum::EnumString, - Serialize, - Deserialize, -)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -pub enum AuthenticationType { - ThreeDs, - NoThreeDs, -} -impl AuthenticationType { - pub fn to_dir_value(&self) -> DirValue { - match self { - Self::ThreeDs => DirValue::AuthenticationType(enums::AuthenticationType::ThreeDs), - Self::NoThreeDs => DirValue::AuthenticationType(enums::AuthenticationType::NoThreeDs), - } - } -} - -impl EuclidAnalysable for AuthenticationType { - fn get_dir_value_for_analysis(&self, rule_name: String) -> Vec<(DirValue, Metadata)> { - let auth = self.to_string(); - - vec![( - self.to_dir_value(), - std::collections::HashMap::from_iter([( - "AUTHENTICATION_TYPE".to_string(), - serde_json::json!({ - "rule_name":rule_name, - "Authentication_type": auth, - }), - )]), - )] - } -} - -#[derive(Debug, Default, Clone, Serialize, Deserialize)] -pub struct ConditionalConfigs { - pub override_3ds: Option, -} -impl EuclidDirFilter for ConditionalConfigs { - const ALLOWED: &'static [DirKeyKind] = &[ - DirKeyKind::PaymentMethod, - DirKeyKind::CardType, - DirKeyKind::CardNetwork, - DirKeyKind::MetaData, - DirKeyKind::PaymentAmount, - DirKeyKind::PaymentCurrency, - DirKeyKind::CaptureMethod, - DirKeyKind::BillingCountry, - DirKeyKind::BusinessCountry, - ]; -} +use euclid::frontend::ast::Program; #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DecisionManagerRecord { pub name: String, - pub program: Program, + pub program: Program, pub created_at: i64, pub modified_at: i64, } @@ -89,12 +17,14 @@ impl events::ApiEventMetric for DecisionManagerRecord { #[serde(deny_unknown_fields)] pub struct ConditionalConfigReq { pub name: Option, - pub algorithm: Option>, + pub algorithm: Option>, } + +#[cfg(feature = "v1")] #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DecisionManagerRequest { pub name: Option, - pub program: Option>, + pub program: Option>, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -111,3 +41,17 @@ impl events::ApiEventMetric for DecisionManager { } pub type DecisionManagerResponse = DecisionManagerRecord; + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct DecisionManagerRequest { + pub name: String, + pub program: Program, +} + +#[cfg(feature = "v2")] +impl events::ApiEventMetric for DecisionManagerRequest { + fn get_api_event_type(&self) -> Option { + Some(events::ApiEventsType::Routing) + } +} diff --git a/crates/common_types/Cargo.toml b/crates/common_types/Cargo.toml index 33dd799f0f6..3f49f546a71 100644 --- a/crates/common_types/Cargo.toml +++ b/crates/common_types/Cargo.toml @@ -15,10 +15,12 @@ v2 = ["common_utils/v2"] diesel = "2.2.3" serde = { version = "1.0.197", features = ["derive"] } serde_json = "1.0.115" +strum = { version = "0.26", features = ["derive"] } utoipa = { version = "4.2.0", features = ["preserve_order", "preserve_path_order"] } common_enums = { version = "0.1.0", path = "../common_enums" } common_utils = { version = "0.1.0", path = "../common_utils"} +euclid = { version = "0.1.0", path = "../euclid" } [lints] workspace = true diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index 08075605628..ea42fbf7a41 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -3,8 +3,12 @@ use std::collections::HashMap; use common_enums::enums; -use common_utils::{errors, impl_to_sql_from_sql_json, types::MinorUnit}; +use common_utils::{errors, events, impl_to_sql_from_sql_json, types::MinorUnit}; use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; +use euclid::frontend::{ + ast::Program, + dir::{DirKeyKind, EuclidDirFilter}, +}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; @@ -21,6 +25,31 @@ pub enum SplitPaymentsRequest { } impl_to_sql_from_sql_json!(SplitPaymentsRequest); +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Hashmap to store mca_id's with product names +pub struct AuthenticationConnectorAccountMap( + HashMap, +); +impl_to_sql_from_sql_json!(AuthenticationConnectorAccountMap); + +impl AuthenticationConnectorAccountMap { + /// fn to get click to pay connector_account_id + pub fn get_click_to_pay_connector_account_id( + &self, + ) -> Result { + self.0 + .get(&enums::AuthenticationProduct::ClickToPay) + .ok_or(errors::ValidationError::MissingRequiredField { + field_name: "authentication_product_id.click_to_pay".to_string(), + }) + .cloned() + } +} + #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -42,26 +71,48 @@ pub struct StripeSplitPaymentRequest { impl_to_sql_from_sql_json!(StripeSplitPaymentRequest); #[derive( - Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, + Serialize, Default, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] #[diesel(sql_type = Jsonb)] -#[serde(deny_unknown_fields)] -/// Hashmap to store mca_id's with product names -pub struct AuthenticationConnectorAccountMap( - HashMap, -); -impl_to_sql_from_sql_json!(AuthenticationConnectorAccountMap); +/// ConditionalConfigs +pub struct ConditionalConfigs { + /// Override 3DS + pub override_3ds: Option, +} +impl EuclidDirFilter for ConditionalConfigs { + const ALLOWED: &'static [DirKeyKind] = &[ + DirKeyKind::PaymentMethod, + DirKeyKind::CardType, + DirKeyKind::CardNetwork, + DirKeyKind::MetaData, + DirKeyKind::PaymentAmount, + DirKeyKind::PaymentCurrency, + DirKeyKind::CaptureMethod, + DirKeyKind::BillingCountry, + DirKeyKind::BusinessCountry, + ]; +} -impl AuthenticationConnectorAccountMap { - /// fn to get click to pay connector_account_id - pub fn get_click_to_pay_connector_account_id( - &self, - ) -> Result { - self.0 - .get(&enums::AuthenticationProduct::ClickToPay) - .ok_or(errors::ValidationError::MissingRequiredField { - field_name: "authentication_product_id.click_to_pay".to_string(), - }) - .cloned() +impl_to_sql_from_sql_json!(ConditionalConfigs); + +#[derive(Serialize, Deserialize, Debug, Clone, FromSqlRow, AsExpression, ToSchema)] +#[diesel(sql_type = Jsonb)] +/// DecisionManagerRecord +pub struct DecisionManagerRecord { + /// Name of the Decision Manager + pub name: String, + /// Program to be executed + pub program: Program, + /// Created at timestamp + pub created_at: i64, +} + +impl events::ApiEventMetric for DecisionManagerRecord { + fn get_api_event_type(&self) -> Option { + Some(events::ApiEventsType::Routing) } } +impl_to_sql_from_sql_json!(DecisionManagerRecord); + +/// DecisionManagerResponse +pub type DecisionManagerResponse = DecisionManagerRecord; diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 06aa21fe9d3..2cbd7deb426 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -310,6 +310,7 @@ pub struct Profile { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } impl Profile { @@ -371,6 +372,7 @@ pub struct ProfileNew { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -416,6 +418,7 @@ pub struct ProfileUpdateInternal { pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -459,6 +462,7 @@ impl ProfileUpdateInternal { max_auto_retries_enabled, is_click_to_pay_enabled, authentication_product_ids, + three_ds_decision_manager_config, } = self; Profile { id: source.id, @@ -527,6 +531,8 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids .or(source.authentication_product_ids), + three_ds_decision_manager_config: three_ds_decision_manager_config + .or(source.three_ds_decision_manager_config), } } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 9b1c3aa7d45..5bd3195f43d 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -224,6 +224,7 @@ diesel::table! { max_auto_retries_enabled -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, + three_ds_decision_manager_config -> Nullable, } } diff --git a/crates/euclid/src/dssa/types.rs b/crates/euclid/src/dssa/types.rs index f8340c31509..54e1820f0e3 100644 --- a/crates/euclid/src/dssa/types.rs +++ b/crates/euclid/src/dssa/types.rs @@ -1,4 +1,4 @@ -use std::fmt; +use std::{collections::HashMap, fmt}; use serde::Serialize; @@ -159,3 +159,25 @@ pub enum ValueType { EnumVariants(Vec), Number, } + +impl EuclidAnalysable for common_enums::AuthenticationType { + fn get_dir_value_for_analysis(&self, rule_name: String) -> Vec<(dir::DirValue, Metadata)> { + let auth = self.to_string(); + + let dir_value = match self { + Self::ThreeDs => dir::DirValue::AuthenticationType(Self::ThreeDs), + Self::NoThreeDs => dir::DirValue::AuthenticationType(Self::NoThreeDs), + }; + + vec![( + dir_value, + HashMap::from_iter([( + "AUTHENTICATION_TYPE".to_string(), + serde_json::json!({ + "rule_name": rule_name, + "Authentication_type": auth, + }), + )]), + )] + } +} diff --git a/crates/euclid_wasm/Cargo.toml b/crates/euclid_wasm/Cargo.toml index 4f967df34ad..9c22ed62312 100644 --- a/crates/euclid_wasm/Cargo.toml +++ b/crates/euclid_wasm/Cargo.toml @@ -22,6 +22,7 @@ v2 = [] [dependencies] api_models = { version = "0.1.0", path = "../api_models", package = "api_models" } common_enums = { version = "0.1.0", path = "../common_enums" } +common_types = { version = "0.1.0", path = "../common_types" } connector_configs = { version = "0.1.0", path = "../connector_configs" } currency_conversion = { version = "0.1.0", path = "../currency_conversion" } euclid = { version = "0.1.0", path = "../euclid", features = [] } diff --git a/crates/euclid_wasm/src/lib.rs b/crates/euclid_wasm/src/lib.rs index b299ced68b0..f977fc45541 100644 --- a/crates/euclid_wasm/src/lib.rs +++ b/crates/euclid_wasm/src/lib.rs @@ -7,7 +7,7 @@ use std::{ }; use api_models::{ - conditional_configs::ConditionalConfigs, enums as api_model_enums, routing::ConnectorSelection, + enums as api_model_enums, routing::ConnectorSelection, surcharge_decision_configs::SurchargeDecisionConfigs, }; use common_enums::RoutableConnectors; @@ -221,7 +221,7 @@ pub fn get_key_type(key: &str) -> Result { #[wasm_bindgen(js_name = getThreeDsKeys)] pub fn get_three_ds_keys() -> JsResult { - let keys = ::ALLOWED; + let keys = ::ALLOWED; Ok(serde_wasm_bindgen::to_value(keys)?) } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 47c505e8ca2..3e49a596d88 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -734,6 +734,7 @@ pub struct Profile { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -777,6 +778,7 @@ pub struct ProfileSetter { pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -826,6 +828,7 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_click_to_pay_enabled: value.is_click_to_pay_enabled, authentication_product_ids: value.authentication_product_ids, + three_ds_decision_manager_config: value.three_ds_decision_manager_config, } } } @@ -879,6 +882,7 @@ pub struct ProfileGeneralUpdate { pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, + pub three_ds_decision_manager_config: Option, } #[cfg(feature = "v2")] @@ -904,6 +908,9 @@ pub enum ProfileUpdate { CollectCvvDuringPaymentUpdate { should_collect_cvv_during_payment: bool, }, + DecisionManagerRecordUpdate { + three_ds_decision_manager_config: common_types::payments::DecisionManagerRecord, + }, } #[cfg(feature = "v2")] @@ -939,6 +946,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_click_to_pay_enabled, authentication_product_ids, + three_ds_decision_manager_config, } = *update; Self { profile_name, @@ -979,6 +987,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids, + three_ds_decision_manager_config, } } ProfileUpdate::RoutingAlgorithmUpdate { @@ -1022,6 +1031,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::ExtendedCardInfoUpdate { is_extended_card_info_enabled, @@ -1063,6 +1073,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::ConnectorAgnosticMitUpdate { is_connector_agnostic_mit_enabled, @@ -1104,6 +1115,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::DefaultRoutingFallbackUpdate { default_fallback_routing, @@ -1145,6 +1157,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::NetworkTokenizationUpdate { is_network_tokenization_enabled, @@ -1186,6 +1199,7 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, }, ProfileUpdate::CollectCvvDuringPaymentUpdate { should_collect_cvv_during_payment, @@ -1227,6 +1241,49 @@ impl From for ProfileUpdateInternal { max_auto_retries_enabled: None, is_click_to_pay_enabled: None, authentication_product_ids: None, + three_ds_decision_manager_config: None, + }, + ProfileUpdate::DecisionManagerRecordUpdate { + three_ds_decision_manager_config, + } => Self { + profile_name: None, + modified_at: now, + return_url: None, + enable_payment_response_hash: None, + payment_response_hash_key: None, + redirect_to_merchant_with_http_post: None, + webhook_details: None, + metadata: None, + is_recon_enabled: None, + applepay_verified_domains: None, + payment_link_config: None, + session_expiry: None, + authentication_connector_details: None, + payout_link_config: None, + is_extended_card_info_enabled: None, + extended_card_info_config: None, + is_connector_agnostic_mit_enabled: None, + use_billing_as_payment_method_billing: None, + collect_shipping_details_from_wallet_connector: None, + collect_billing_details_from_wallet_connector: None, + outgoing_webhook_custom_http_headers: None, + always_collect_billing_details_from_wallet_connector: None, + always_collect_shipping_details_from_wallet_connector: None, + routing_algorithm_id: None, + payout_routing_algorithm_id: None, + order_fulfillment_time: None, + order_fulfillment_time_origin: None, + frm_routing_algorithm_id: None, + default_fallback_routing: None, + should_collect_cvv_during_payment: None, + tax_connector_id: None, + is_tax_connector_enabled: None, + is_network_tokenization_enabled: None, + is_auto_retries_enabled: None, + max_auto_retries_enabled: None, + is_click_to_pay_enabled: None, + authentication_product_ids: None, + three_ds_decision_manager_config: Some(three_ds_decision_manager_config), }, } } @@ -1288,6 +1345,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + three_ds_decision_manager_config: self.three_ds_decision_manager_config, }) } @@ -1358,6 +1416,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, + three_ds_decision_manager_config: item.three_ds_decision_manager_config, }) } .await @@ -1415,6 +1474,7 @@ impl super::behaviour::Conversion for Profile { max_auto_retries_enabled: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + three_ds_decision_manager_config: self.three_ds_decision_manager_config, }) } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 356b4a31cc6..3ec9d76397b 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3798,6 +3798,7 @@ impl ProfileCreateBridge for api::ProfileCreate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + three_ds_decision_manager_config: None, })) } } @@ -4147,6 +4148,7 @@ impl ProfileUpdateBridge for api::ProfileUpdate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, + three_ds_decision_manager_config: None, }, ))) } diff --git a/crates/router/src/core/conditional_config.rs b/crates/router/src/core/conditional_config.rs index 9ef409268fb..66ad750bbbe 100644 --- a/crates/router/src/core/conditional_config.rs +++ b/crates/router/src/core/conditional_config.rs @@ -1,7 +1,11 @@ +#[cfg(feature = "v2")] +use api_models::conditional_configs::DecisionManagerRequest; use api_models::conditional_configs::{ DecisionManager, DecisionManagerRecord, DecisionManagerResponse, }; use common_utils::ext_traits::StringExt; +#[cfg(feature = "v2")] +use common_utils::types::keymanager::KeyManagerState; use error_stack::ResultExt; use crate::{ @@ -10,15 +14,57 @@ use crate::{ services::api as service_api, types::domain, }; - #[cfg(feature = "v2")] pub async fn upsert_conditional_config( - _state: SessionState, - _key_store: domain::MerchantKeyStore, - _merchant_account: domain::MerchantAccount, - _request: DecisionManager, -) -> RouterResponse { - todo!() + state: SessionState, + key_store: domain::MerchantKeyStore, + request: DecisionManagerRequest, + profile: domain::Profile, +) -> RouterResponse { + use common_utils::ext_traits::OptionExt; + + let key_manager_state: &KeyManagerState = &(&state).into(); + let db = &*state.store; + let name = request.name; + let program = request.program; + let timestamp = common_utils::date_time::now_unix_timestamp(); + + euclid::frontend::ast::lowering::lower_program(program.clone()) + .change_context(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid Request Data".to_string(), + }) + .attach_printable("The Request has an Invalid Comparison")?; + + let decision_manager_record = common_types::payments::DecisionManagerRecord { + name, + program, + created_at: timestamp, + }; + + let business_profile_update = domain::ProfileUpdate::DecisionManagerRecordUpdate { + three_ds_decision_manager_config: decision_manager_record, + }; + let updated_profile = db + .update_profile_by_profile_id( + key_manager_state, + &key_store, + profile, + business_profile_update, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update decision manager record in business profile")?; + + Ok(service_api::ApplicationResponse::Json( + updated_profile + .three_ds_decision_manager_config + .clone() + .get_required_value("three_ds_decision_manager_config") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "Failed to get updated decision manager record in business profile", + )?, + )) } #[cfg(feature = "v1")] @@ -204,6 +250,7 @@ pub async fn delete_conditional_config( Ok(service_api::ApplicationResponse::StatusOk) } +#[cfg(feature = "v1")] pub async fn retrieve_conditional_config( state: SessionState, merchant_account: domain::MerchantAccount, @@ -229,3 +276,27 @@ pub async fn retrieve_conditional_config( }; Ok(service_api::ApplicationResponse::Json(response)) } + +#[cfg(feature = "v2")] +pub async fn retrieve_conditional_config( + state: SessionState, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state: &KeyManagerState = &(&state).into(); + let profile_id = profile.get_id(); + + let record = profile + .three_ds_decision_manager_config + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("The Conditional Config Record was not found")?; + + let response = common_types::payments::DecisionManagerRecord { + name: record.name, + program: record.program, + created_at: record.created_at, + }; + Ok(service_api::ApplicationResponse::Json(response)) +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 47d2024834e..33540cf0b4b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -109,7 +109,7 @@ use crate::{ api::{self, ConnectorCallType, ConnectorCommon}, domain, storage::{self, enums as storage_enums, payment_attempt::PaymentAttemptExt}, - transformers::{ForeignInto, ForeignTryInto}, + transformers::ForeignTryInto, }, utils::{ self, add_apple_pay_flow_metrics, add_connector_http_status_code_metrics, Encode, @@ -1145,7 +1145,7 @@ where Ok(payment_dsl_data .payment_attempt .authentication_type - .or(output.override_3ds.map(ForeignInto::foreign_into)) + .or(output.override_3ds) .or(Some(storage_enums::AuthenticationType::NoThreeDs))) } diff --git a/crates/router/src/core/payments/conditional_configs.rs b/crates/router/src/core/payments/conditional_configs.rs index cc6cc9cd743..d511c0fd6a7 100644 --- a/crates/router/src/core/payments/conditional_configs.rs +++ b/crates/router/src/core/payments/conditional_configs.rs @@ -1,9 +1,4 @@ -mod transformers; - -use api_models::{ - conditional_configs::{ConditionalConfigs, DecisionManagerRecord}, - routing, -}; +use api_models::{conditional_configs::DecisionManagerRecord, routing}; use common_utils::ext_traits::StringExt; use error_stack::ResultExt; use euclid::backend::{self, inputs as dsl_inputs, EuclidBackend}; @@ -23,11 +18,11 @@ pub async fn perform_decision_management( algorithm_ref: routing::RoutingAlgorithmRef, merchant_id: &common_utils::id_type::MerchantId, payment_data: &core_routing::PaymentsDslInput<'_>, -) -> ConditionalConfigResult { +) -> ConditionalConfigResult { let algorithm_id = if let Some(id) = algorithm_ref.config_algo_id { id } else { - return Ok(ConditionalConfigs::default()); + return Ok(common_types::payments::ConditionalConfigs::default()); }; let db = &*state.store; @@ -64,8 +59,8 @@ pub async fn perform_decision_management( pub fn execute_dsl_and_get_conditional_config( backend_input: dsl_inputs::BackendInput, - interpreter: &backend::VirInterpreterBackend, -) -> ConditionalConfigResult { + interpreter: &backend::VirInterpreterBackend, +) -> ConditionalConfigResult { let routing_output = interpreter .execute(backend_input) .map(|out| out.connector_selection) diff --git a/crates/router/src/core/payments/conditional_configs/transformers.rs b/crates/router/src/core/payments/conditional_configs/transformers.rs deleted file mode 100644 index 023bd65dcf4..00000000000 --- a/crates/router/src/core/payments/conditional_configs/transformers.rs +++ /dev/null @@ -1,22 +0,0 @@ -use api_models::{self, conditional_configs}; -use diesel_models::enums as storage_enums; -use euclid::enums as dsl_enums; - -use crate::types::transformers::ForeignFrom; -impl ForeignFrom for conditional_configs::AuthenticationType { - fn foreign_from(from: dsl_enums::AuthenticationType) -> Self { - match from { - dsl_enums::AuthenticationType::ThreeDs => Self::ThreeDs, - dsl_enums::AuthenticationType::NoThreeDs => Self::NoThreeDs, - } - } -} - -impl ForeignFrom for storage_enums::AuthenticationType { - fn foreign_from(from: conditional_configs::AuthenticationType) -> Self { - match from { - conditional_configs::AuthenticationType::ThreeDs => Self::ThreeDs, - conditional_configs::AuthenticationType::NoThreeDs => Self::NoThreeDs, - } - } -} diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index ca14e3e7e48..84de32483a8 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -197,17 +197,6 @@ pub async fn update_merchant_active_algorithm_ref( Ok(()) } -#[cfg(feature = "v2")] -pub async fn update_merchant_active_algorithm_ref( - _state: &SessionState, - _key_store: &domain::MerchantKeyStore, - _config_key: cache::CacheKind<'_>, - _algorithm_id: routing_types::RoutingAlgorithmRef, -) -> RouterResult<()> { - // TODO: handle updating the active routing algorithm for v2 in merchant account - todo!() -} - #[cfg(feature = "v1")] pub async fn update_profile_active_algorithm_ref( db: &dyn StorageInterface, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 17ab056e856..7f7ea767108 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1832,7 +1832,12 @@ impl Profile { &TransactionType::Payment, ) }, - ))), + ))) + .service( + web::resource("/decision") + .route(web::put().to(routing::upsert_decision_manager_config)) + .route(web::get().to(routing::retrieve_decision_manager_config)), + ), ) } } diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index 76852bf12ef..e7227e1f752 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -688,7 +688,7 @@ pub async fn retrieve_surcharge_decision_manager_config( .await } -#[cfg(feature = "olap")] +#[cfg(all(feature = "olap", feature = "v1"))] #[instrument(skip_all)] pub async fn upsert_decision_manager_config( state: web::Data, @@ -726,6 +726,44 @@ pub async fn upsert_decision_manager_config( .await } +#[cfg(all(feature = "olap", feature = "v2"))] +#[instrument(skip_all)] +pub async fn upsert_decision_manager_config( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> impl Responder { + let flow = Flow::DecisionManagerUpsertConfig; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + json_payload.into_inner(), + |state, auth: auth::AuthenticationData, update_decision, _| { + conditional_config::upsert_conditional_config( + state, + auth.key_store, + update_decision, + auth.profile, + ) + }, + #[cfg(not(feature = "release"))] + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::ProfileThreeDsDecisionManagerWrite, + }, + req.headers(), + ), + #[cfg(feature = "release")] + &auth::JWTAuth { + permission: Permission::ProfileThreeDsDecisionManagerWrite, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "olap")] #[instrument(skip_all)] pub async fn delete_decision_manager_config( @@ -762,6 +800,40 @@ pub async fn delete_decision_manager_config( .await } +#[cfg(all(feature = "olap", feature = "v2"))] +#[cfg(feature = "olap")] +#[instrument(skip_all)] +pub async fn retrieve_decision_manager_config( + state: web::Data, + req: HttpRequest, +) -> impl Responder { + let flow = Flow::DecisionManagerRetrieveConfig; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + (), + |state, auth: auth::AuthenticationData, _, _| { + conditional_config::retrieve_conditional_config(state, auth.key_store, auth.profile) + }, + #[cfg(not(feature = "release"))] + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::ProfileThreeDsDecisionManagerWrite, + }, + req.headers(), + ), + #[cfg(feature = "release")] + &auth::JWTAuth { + permission: Permission::ProfileThreeDsDecisionManagerWrite, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all(feature = "olap", feature = "v1"))] #[cfg(feature = "olap")] #[instrument(skip_all)] pub async fn retrieve_decision_manager_config( diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 04d3d436736..5396fff2897 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -45,7 +45,7 @@ generate_permissions! { }, ThreeDsDecisionManager: { scopes: [Read, Write], - entities: [Merchant] + entities: [Merchant, Profile] }, SurchargeDecisionManager: { scopes: [Read, Write], diff --git a/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql new file mode 100644 index 00000000000..a5295472f12 --- /dev/null +++ b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE business_profile +DROP COLUMN IF EXISTS three_ds_decision_manager_config; \ No newline at end of file diff --git a/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql new file mode 100644 index 00000000000..e329a39f1a9 --- /dev/null +++ b/v2_migrations/2025-01-13-181842_add_three_ds_decision_manager_config_in_profile/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE business_profile +ADD COLUMN IF NOT EXISTS three_ds_decision_manager_config jsonb; \ No newline at end of file From 698a0aa75af646107ac796f719b51e74530f11dc Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:08:18 +0530 Subject: [PATCH 040/133] fix(connector): [Authorizedotnet] fix deserialization error for Paypal while canceling payment (#7141) --- .../src/connector/authorizedotnet/transformers.rs | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 8e0af603a3d..c430191a1ec 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -1872,13 +1872,13 @@ pub struct PaypalPaymentConfirm { #[derive(Debug, Serialize, Deserialize)] pub struct Paypal { #[serde(rename = "payerID")] - payer_id: Secret, + payer_id: Option>, } #[derive(Debug, Serialize, Deserialize)] pub struct PaypalQueryParams { #[serde(rename = "PayerID")] - payer_id: Secret, + payer_id: Option>, } impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsCompleteAuthorizeRouterData>> @@ -1895,10 +1895,13 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::PaymentsCompleteAuthorizeRouterD .as_ref() .and_then(|redirect_response| redirect_response.params.as_ref()) .ok_or(errors::ConnectorError::ResponseDeserializationFailed)?; - let payer_id: Secret = - serde_urlencoded::from_str::(params.peek()) - .change_context(errors::ConnectorError::ResponseDeserializationFailed)? - .payer_id; + + let query_params: PaypalQueryParams = serde_urlencoded::from_str(params.peek()) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .attach_printable("Failed to parse connector response")?; + + let payer_id = query_params.payer_id; + let transaction_type = match item.router_data.request.capture_method { Some(enums::CaptureMethod::Manual) => Ok(TransactionType::ContinueAuthorization), Some(enums::CaptureMethod::SequentialAutomatic) From 8917235b4c1c606cba92539b9cb50449fc70474a Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:08:40 +0530 Subject: [PATCH 041/133] fix(core): Add payment_link_data in PaymentData for Psync (#7137) Co-authored-by: pranav-arjunan --- crates/router/src/core/payments.rs | 18 ++++++++++++++++++ .../core/payments/operations/payment_status.rs | 9 ++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 33540cf0b4b..dce89107afd 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4843,6 +4843,24 @@ impl CustomerDetailsExt for CustomerDetails { } } +pub async fn get_payment_link_response_from_id( + state: &SessionState, + payment_link_id: &str, +) -> CustomResult { + let db = &*state.store; + + let payment_link_object = db + .find_payment_link_by_payment_link_id(payment_link_id) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentLinkNotFound)?; + + Ok(api_models::payments::PaymentLinkResponse { + link: payment_link_object.link_to_pay.clone(), + secure_link: payment_link_object.secure_link, + payment_link_id: payment_link_object.payment_link_id, + }) +} + #[cfg(feature = "v1")] pub fn if_not_create_change_operation<'a, Op, F>( status: storage_enums::IntentStatus, diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index dd28043ba2b..44f0c9172f9 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -467,6 +467,13 @@ async fn get_tracker_for_sync< }).await .transpose()?; + let payment_link_data = payment_intent + .payment_link_id + .as_ref() + .async_map(|id| crate::core::payments::get_payment_link_response_from_id(state, id)) + .await + .transpose()?; + let payment_data = PaymentData { flow: PhantomData, payment_intent, @@ -512,7 +519,7 @@ async fn get_tracker_for_sync< ephemeral_key: None, multiple_capture_data, redirect_response: None, - payment_link_data: None, + payment_link_data, surcharge_details: None, frm_message: frm_response, incremental_authorization_details: None, From d443a4cf1ee7bb9f5daa5147bd2854b3e4f4c76d Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 5 Feb 2025 19:10:48 +0530 Subject: [PATCH 042/133] fix(connector): [worldpay] remove threeDS data from Authorize request for NTI flows (#7097) --- .../src/connectors/worldpay/requests.rs | 2 +- .../src/connectors/worldpay/transformers.rs | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs index 52ae2c4f16f..6e19c20edab 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs @@ -32,7 +32,7 @@ pub struct Instruction { pub value: PaymentValue, #[serde(skip_serializing_if = "Option::is_none")] pub debt_repayment: Option, - #[serde(rename = "threeDS")] + #[serde(rename = "threeDS", skip_serializing_if = "Option::is_none")] pub three_ds: Option, /// For setting up mandates pub token_creation: Option, diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index 6ae507790dd..e4656021a59 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -390,8 +390,14 @@ fn create_three_ds_request( router_data: &T, is_mandate_payment: bool, ) -> Result, error_stack::Report> { - match router_data.get_auth_type() { - enums::AuthenticationType::ThreeDs => { + match ( + router_data.get_auth_type(), + router_data.get_payment_method_data(), + ) { + // 3DS for NTI flow + (_, PaymentMethodData::CardDetailsForNetworkTransactionId(_)) => Ok(None), + // 3DS for regular payments + (enums::AuthenticationType::ThreeDs, _) => { let browser_info = router_data.get_browser_info().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "browser_info", @@ -439,6 +445,7 @@ fn create_three_ds_request( }, })) } + // Non 3DS _ => Ok(None), } } From ce2485c3c77d86a2bce01d20c410ae11ac08c555 Mon Sep 17 00:00:00 2001 From: Sagnik Mitra <83326850+ImSagnik007@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:11:41 +0530 Subject: [PATCH 043/133] feat(connector): [INESPAY] Integrate Sepa Bank Debit (#6755) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 2 + api-reference/openapi_spec.json | 2 + config/development.toml | 3 + crates/common_enums/src/connector_enums.rs | 7 +- crates/connector_configs/src/connector.rs | 4 +- .../src/connectors/inespay.rs | 221 ++++++-- .../src/connectors/inespay/transformers.rs | 479 ++++++++++++++---- crates/router/src/core/admin.rs | 8 +- crates/router/src/types/api.rs | 6 +- crates/router/src/types/transformers.rs | 2 +- 10 files changed, 574 insertions(+), 160 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index ebb93fd8a11..7556a4e7765 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6716,6 +6716,7 @@ "gocardless", "gpayments", "helcim", + "inespay", "iatapay", "itaubank", "jpmorgan", @@ -18734,6 +18735,7 @@ "gocardless", "helcim", "iatapay", + "inespay", "itaubank", "jpmorgan", "klarna", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index bce5ac3c8e8..ea0a95c1d57 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9298,6 +9298,7 @@ "gocardless", "gpayments", "helcim", + "inespay", "iatapay", "itaubank", "jpmorgan", @@ -23995,6 +23996,7 @@ "gocardless", "helcim", "iatapay", + "inespay", "itaubank", "jpmorgan", "klarna", diff --git a/config/development.toml b/config/development.toml index 2b81123d873..473c453d1c8 100644 --- a/config/development.toml +++ b/config/development.toml @@ -565,6 +565,9 @@ debit = { currency = "USD" } [pm_filters.fiuu] duit_now = { country = "MY", currency = "MYR" } +[pm_filters.inespay] +sepa = { currency = "EUR" } + [tokenization] stripe = { long_lived_token = false, payment_method = "wallet", payment_method_type = { type = "disable_only", list = "google_pay" } } checkout = { long_lived_token = false, payment_method = "wallet", apple_pay_pre_decrypt_flow = "network_tokenization" } diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 288520722c3..28dbf33d97d 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -85,7 +85,7 @@ pub enum RoutableConnectors { Gocardless, Helcim, Iatapay, - // Inespay, + Inespay, Itaubank, Jpmorgan, Klarna, @@ -221,7 +221,7 @@ pub enum Connector { Gocardless, Gpayments, Helcim, - // Inespay, + Inespay, Iatapay, Itaubank, Jpmorgan, @@ -370,7 +370,7 @@ impl Connector { | Self::Gpayments | Self::Helcim | Self::Iatapay - // | Self::Inespay + | Self::Inespay | Self::Itaubank | Self::Jpmorgan | Self::Klarna @@ -540,6 +540,7 @@ impl From for Connector { RoutableConnectors::Plaid => Self::Plaid, RoutableConnectors::Zsl => Self::Zsl, RoutableConnectors::Xendit => Self::Xendit, + RoutableConnectors::Inespay => Self::Inespay, } } } diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 791a0679ea3..3175bbde8f2 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -196,7 +196,7 @@ pub struct ConnectorConfig { pub gocardless: Option, pub gpayments: Option, pub helcim: Option, - // pub inespay: Option, + pub inespay: Option, pub jpmorgan: Option, pub klarna: Option, pub mifinity: Option, @@ -358,7 +358,7 @@ impl ConnectorConfig { Connector::Gocardless => Ok(connector_data.gocardless), Connector::Gpayments => Ok(connector_data.gpayments), Connector::Helcim => Ok(connector_data.helcim), - // Connector::Inespay => Ok(connector_data.inespay), + Connector::Inespay => Ok(connector_data.inespay), Connector::Jpmorgan => Ok(connector_data.jpmorgan), Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), diff --git a/crates/hyperswitch_connectors/src/connectors/inespay.rs b/crates/hyperswitch_connectors/src/connectors/inespay.rs index 3d3ea346b78..41ae798ade3 100644 --- a/crates/hyperswitch_connectors/src/connectors/inespay.rs +++ b/crates/hyperswitch_connectors/src/connectors/inespay.rs @@ -1,12 +1,15 @@ pub mod transformers; +use base64::Engine; use common_utils::{ + consts::BASE64_ENGINE, + crypto, errors::CustomResult, - ext_traits::BytesExt, + ext_traits::{ByteSliceExt, BytesExt}, request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ @@ -36,7 +39,8 @@ use hyperswitch_interfaces::{ types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, Secret}; +use ring::hmac; use transformers as inespay; use crate::{constants::headers, types::ResponseRouterData, utils}; @@ -86,8 +90,8 @@ where headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let mut auth_headers = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut auth_headers); Ok(header) } } @@ -98,10 +102,7 @@ impl ConnectorCommon for Inespay { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + api::CurrencyUnit::Minor } fn common_get_content_type(&self) -> &'static str { @@ -118,10 +119,16 @@ impl ConnectorCommon for Inespay { ) -> CustomResult)>, errors::ConnectorError> { let auth = inespay::InespayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) + Ok(vec![ + ( + headers::AUTHORIZATION.to_string(), + auth.authorization.expose().into_masked(), + ), + ( + headers::X_API_KEY.to_string(), + auth.api_key.expose().into_masked(), + ), + ]) } fn build_error_response( @@ -139,9 +146,9 @@ impl ConnectorCommon for Inespay { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.status, + message: response.status_desc, + reason: None, attempt_status: None, connector_transaction_id: None, }) @@ -176,9 +183,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payins/single/init", self.base_url(connectors))) } fn get_request_body( @@ -192,9 +199,19 @@ impl ConnectorIntegration { + let connector_router_data = inespay::InespayRouterData::from((amount, req)); + let connector_req = + inespay::InespayPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + _ => Err(errors::ConnectorError::CurrencyNotSupported { + message: req.request.currency.to_string(), + connector: "Inespay", + } + .into()), + } } fn build_request( @@ -262,10 +279,20 @@ impl ConnectorIntegration for Ine fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}{}{}", + self.base_url(connectors), + "/payins/single/", + connector_payment_id, + )) } fn build_request( @@ -289,7 +316,7 @@ impl ConnectorIntegration for Ine event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: inespay::InespayPaymentsResponse = res + let response: inespay::InespayPSyncResponse = res .response .parse_struct("inespay PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -406,9 +433,9 @@ impl ConnectorIntegration for Inespay fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds/init", self.base_url(connectors))) } fn get_request_body( @@ -452,9 +479,9 @@ impl ConnectorIntegration for Inespay event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult, errors::ConnectorError> { - let response: inespay::RefundResponse = res + let response: inespay::InespayRefundsResponse = res .response - .parse_struct("inespay RefundResponse") + .parse_struct("inespay InespayRefundsResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); @@ -489,10 +516,20 @@ impl ConnectorIntegration for Inespay { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_refund_id = req + .request + .connector_refund_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorRefundID)?; + Ok(format!( + "{}{}{}", + self.base_url(connectors), + "/refunds/", + connector_refund_id, + )) } fn build_request( @@ -519,7 +556,7 @@ impl ConnectorIntegration for Inespay { event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: inespay::RefundResponse = res + let response: inespay::InespayRSyncResponse = res .response .parse_struct("inespay RefundSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -541,27 +578,133 @@ impl ConnectorIntegration for Inespay { } } +fn get_webhook_body( + body: &[u8], +) -> CustomResult { + let notif_item: inespay::InespayWebhookEvent = + serde_urlencoded::from_bytes::(body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let encoded_data_return = notif_item.data_return; + let decoded_data_return = BASE64_ENGINE + .decode(encoded_data_return) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + let data_return: inespay::InespayWebhookEventData = decoded_data_return + .parse_struct("inespay InespayWebhookEventData") + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(data_return) +} + #[async_trait::async_trait] impl webhooks::IncomingWebhook for Inespay { - fn get_webhook_object_reference_id( + fn get_webhook_source_verification_algorithm( &self, _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Ok(Box::new(crypto::HmacSha256)) + } + + fn get_webhook_source_verification_signature( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let notif_item = serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + + Ok(notif_item.signature_data_return.as_bytes().to_owned()) + } + + fn get_webhook_source_verification_message( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + _merchant_id: &common_utils::id_type::MerchantId, + _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, + ) -> CustomResult, errors::ConnectorError> { + let notif_item = serde_urlencoded::from_bytes::(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + + Ok(notif_item.data_return.into_bytes()) + } + + async fn verify_webhook_source( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, + merchant_id: &common_utils::id_type::MerchantId, + connector_webhook_details: Option, + _connector_account_details: crypto::Encryptable>, + connector_label: &str, + ) -> CustomResult { + let connector_webhook_secrets = self + .get_webhook_source_verification_merchant_secret( + merchant_id, + connector_label, + connector_webhook_details, + ) + .await?; + let signature = + self.get_webhook_source_verification_signature(request, &connector_webhook_secrets)?; + + let message = self.get_webhook_source_verification_message( + request, + merchant_id, + &connector_webhook_secrets, + )?; + let secret = connector_webhook_secrets.secret; + + let signing_key = hmac::Key::new(hmac::HMAC_SHA256, &secret); + let signed_message = hmac::sign(&signing_key, &message); + let computed_signature = hex::encode(signed_message.as_ref()); + let payload_sign = BASE64_ENGINE.encode(computed_signature); + Ok(payload_sign.as_bytes().eq(&signature)) + } + + fn get_webhook_object_reference_id( + &self, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let data_return = get_webhook_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + match data_return { + inespay::InespayWebhookEventData::Payment(data) => { + Ok(api_models::webhooks::ObjectReferenceId::PaymentId( + api_models::payments::PaymentIdType::ConnectorTransactionId( + data.single_payin_id, + ), + )) + } + inespay::InespayWebhookEventData::Refund(data) => { + Ok(api_models::webhooks::ObjectReferenceId::RefundId( + api_models::webhooks::RefundIdType::ConnectorRefundId(data.refund_id), + )) + } + } } fn get_webhook_event_type( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let data_return = get_webhook_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(api_models::webhooks::IncomingWebhookEvent::from( + data_return, + )) } fn get_webhook_resource_object( &self, - _request: &webhooks::IncomingWebhookRequestDetails<'_>, + request: &webhooks::IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { - Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + let data_return = get_webhook_body(request.body) + .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; + Ok(match data_return { + inespay::InespayWebhookEventData::Payment(payment_webhook_data) => { + Box::new(payment_webhook_data) + } + inespay::InespayWebhookEventData::Refund(refund_webhook_data) => { + Box::new(refund_webhook_data) + } + }) } } diff --git a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs index 296d76546c8..2739aa66bee 100644 --- a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs @@ -1,31 +1,33 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::{ + request::Method, + types::{MinorUnit, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + payment_method_data::{BankDebitData, PaymentMethodData}, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types::{PaymentsAuthorizeRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; +use url::Url; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + utils::{self, PaymentsAuthorizeRequestData, RouterData as _}, }; - -//TODO: Fill the struct with respective fields pub struct InespayRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMinorUnit, pub router_data: T, } impl From<(StringMinorUnit, T)> for InespayRouterData { fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts Self { amount, router_data: item, @@ -33,20 +35,15 @@ impl From<(StringMinorUnit, T)> for InespayRouterData { } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct InespayPaymentsRequest { + description: String, amount: StringMinorUnit, - card: InespayCard, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct InespayCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + reference: String, + debtor_account: Option>, + success_link_redirect: Option, + notif_url: Option, } impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymentsRequest { @@ -55,17 +52,17 @@ impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymen item: &InespayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = InespayCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; + PaymentMethodData::BankDebit(BankDebitData::SepaBankDebit { iban, .. }) => { + let order_id = item.router_data.connector_request_reference_id.clone(); + let webhook_url = item.router_data.request.get_webhook_url()?; + let return_url = item.router_data.request.get_router_return_url()?; Ok(Self { + description: item.router_data.get_description()?, amount: item.amount.clone(), - card, + reference: order_id, + debtor_account: Some(iban), + success_link_redirect: Some(return_url), + notif_url: Some(webhook_url), }) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -73,156 +70,422 @@ impl TryFrom<&InespayRouterData<&PaymentsAuthorizeRouterData>> for InespayPaymen } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct InespayAuthType { pub(super) api_key: Secret, + pub authorization: Secret, } impl TryFrom<&ConnectorAuthType> for InespayAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { api_key: api_key.to_owned(), + authorization: key1.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum InespayPaymentStatus { - Succeeded, + +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InespayPaymentsResponseData { + status: String, + status_desc: String, + single_payin_id: String, + single_payin_link: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum InespayPaymentsResponse { + InespayPaymentsData(InespayPaymentsResponseData), + InespayPaymentsError(InespayErrorResponse), +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let (status, response) = match item.response { + InespayPaymentsResponse::InespayPaymentsData(data) => { + let redirection_url = Url::parse(data.single_payin_link.as_str()) + .change_context(errors::ConnectorError::ParsingFailed)?; + let redirection_data = RedirectForm::from((redirection_url, Method::Get)); + + ( + common_enums::AttemptStatus::AuthenticationPending, + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + data.single_payin_id.clone(), + ), + redirection_data: Box::new(Some(redirection_data)), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ) + } + InespayPaymentsResponse::InespayPaymentsError(data) => ( + common_enums::AttemptStatus::Failure, + Err(ErrorResponse { + code: data.status.clone(), + message: data.status_desc.clone(), + reason: Some(data.status_desc.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ), + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InespayPSyncStatus { + Ok, + Created, + Opened, + BankSelected, + Initiated, + Pending, + Aborted, + Unfinished, + Rejected, + Cancelled, + PartiallyAccepted, Failed, - #[default] - Processing, + Settled, + PartRefunded, + Refunded, } -impl From for common_enums::AttemptStatus { - fn from(item: InespayPaymentStatus) -> Self { +impl From for common_enums::AttemptStatus { + fn from(item: InespayPSyncStatus) -> Self { match item { - InespayPaymentStatus::Succeeded => Self::Charged, - InespayPaymentStatus::Failed => Self::Failure, - InespayPaymentStatus::Processing => Self::Authorizing, + InespayPSyncStatus::Ok | InespayPSyncStatus::Settled => Self::Charged, + InespayPSyncStatus::Created + | InespayPSyncStatus::Opened + | InespayPSyncStatus::BankSelected + | InespayPSyncStatus::Initiated + | InespayPSyncStatus::Pending + | InespayPSyncStatus::Unfinished + | InespayPSyncStatus::PartiallyAccepted => Self::AuthenticationPending, + InespayPSyncStatus::Aborted + | InespayPSyncStatus::Rejected + | InespayPSyncStatus::Cancelled + | InespayPSyncStatus::Failed => Self::Failure, + InespayPSyncStatus::PartRefunded | InespayPSyncStatus::Refunded => Self::AutoRefunded, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct InespayPaymentsResponse { - status: InespayPaymentStatus, - id: String, +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InespayPSyncResponseData { + cod_status: InespayPSyncStatus, + status_desc: String, + single_payin_id: String, + single_payin_link: String, } -impl TryFrom> +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum InespayPSyncResponse { + InespayPSyncData(InespayPSyncResponseData), + InespayPSyncWebhook(InespayPaymentWebhookData), + InespayPSyncError(InespayErrorResponse), +} + +impl TryFrom> for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, ) -> Result { - Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), - mandate_reference: Box::new(None), - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + InespayPSyncResponse::InespayPSyncData(data) => { + let redirection_url = Url::parse(data.single_payin_link.as_str()) + .change_context(errors::ConnectorError::ParsingFailed)?; + let redirection_data = RedirectForm::from((redirection_url, Method::Get)); + + Ok(Self { + status: common_enums::AttemptStatus::from(data.cod_status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + data.single_payin_id.clone(), + ), + redirection_data: Box::new(Some(redirection_data)), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + InespayPSyncResponse::InespayPSyncWebhook(data) => { + let status = enums::AttemptStatus::from(data.cod_status); + Ok(Self { + status, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + data.single_payin_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + InespayPSyncResponse::InespayPSyncError(data) => Ok(Self { + response: Err(ErrorResponse { + code: data.status.clone(), + message: data.status_desc.clone(), + reason: Some(data.status_desc.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ..item.data }), - ..item.data - }) + } } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct InespayRefundRequest { - pub amount: StringMinorUnit, + single_payin_id: String, + amount: Option, } impl TryFrom<&InespayRouterData<&RefundsRouterData>> for InespayRefundRequest { type Error = error_stack::Report; fn try_from(item: &InespayRouterData<&RefundsRouterData>) -> Result { + let amount = utils::convert_back_amount_to_minor_units( + &StringMinorUnitForConnector, + item.amount.to_owned(), + item.router_data.request.currency, + )?; Ok(Self { - amount: item.amount.to_owned(), + single_payin_id: item.router_data.request.connector_transaction_id.clone(), + amount: Some(amount), }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, +#[derive(Debug, Serialize, Default, Deserialize, Clone, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum InespayRSyncStatus { + Confirmed, #[default] - Processing, + Pending, + Rejected, + Denied, + Reversed, + Mistake, } -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { +impl From for enums::RefundStatus { + fn from(item: InespayRSyncStatus) -> Self { match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping + InespayRSyncStatus::Confirmed => Self::Success, + InespayRSyncStatus::Pending => Self::Pending, + InespayRSyncStatus::Rejected + | InespayRSyncStatus::Denied + | InespayRSyncStatus::Reversed + | InespayRSyncStatus::Mistake => Self::Failure, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, +#[serde(rename_all = "camelCase")] +pub struct RefundsData { + status: String, + status_desc: String, + refund_id: String, } -impl TryFrom> for RefundsRouterData { +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum InespayRefundsResponse { + InespayRefundsData(RefundsData), + InespayRefundsError(InespayErrorResponse), +} + +impl TryFrom> + for RefundsRouterData +{ type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + match item.response { + InespayRefundsResponse::InespayRefundsData(data) => Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: data.refund_id, + refund_status: enums::RefundStatus::Pending, + }), + ..item.data }), - ..item.data - }) + InespayRefundsResponse::InespayRefundsError(data) => Ok(Self { + response: Err(ErrorResponse { + code: data.status.clone(), + message: data.status_desc.clone(), + reason: Some(data.status_desc.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + ..item.data + }), + } } } -impl TryFrom> for RefundsRouterData { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct InespayRSyncResponseData { + cod_status: InespayRSyncStatus, + status_desc: String, + refund_id: String, +} + +#[derive(Debug, Deserialize, Serialize)] +#[serde(untagged)] +pub enum InespayRSyncResponse { + InespayRSyncData(InespayRSyncResponseData), + InespayRSyncWebhook(InespayRefundWebhookData), + InespayRSyncError(InespayErrorResponse), +} + +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + let response = match item.response { + InespayRSyncResponse::InespayRSyncData(data) => Ok(RefundsResponseData { + connector_refund_id: data.refund_id, + refund_status: enums::RefundStatus::from(data.cod_status), + }), + InespayRSyncResponse::InespayRSyncWebhook(data) => Ok(RefundsResponseData { + connector_refund_id: data.refund_id, + refund_status: enums::RefundStatus::from(data.cod_status), + }), + InespayRSyncResponse::InespayRSyncError(data) => Err(ErrorResponse { + code: data.status.clone(), + message: data.status_desc.clone(), + reason: Some(data.status_desc.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, }), + }; + Ok(Self { + response, ..item.data }) } } -//TODO: Fill the struct with respective fields +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InespayPaymentWebhookData { + pub single_payin_id: String, + pub cod_status: InespayPSyncStatus, + pub description: String, + pub amount: MinorUnit, + pub reference: String, + pub creditor_account: Secret, + pub debtor_name: Secret, + pub debtor_account: Secret, + pub custom_data: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InespayRefundWebhookData { + pub refund_id: String, + pub simple_payin_id: String, + pub cod_status: InespayRSyncStatus, + pub description: String, + pub amount: MinorUnit, + pub reference: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +#[serde(untagged)] +pub enum InespayWebhookEventData { + Payment(InespayPaymentWebhookData), + Refund(InespayRefundWebhookData), +} + +#[derive(Serialize, Deserialize, Debug)] +#[serde(rename_all = "camelCase")] +pub struct InespayWebhookEvent { + pub data_return: String, + pub signature_data_return: String, +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct InespayErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub status: String, + pub status_desc: String, +} + +impl From for api_models::webhooks::IncomingWebhookEvent { + fn from(item: InespayWebhookEventData) -> Self { + match item { + InespayWebhookEventData::Payment(payment_data) => match payment_data.cod_status { + InespayPSyncStatus::Ok | InespayPSyncStatus::Settled => Self::PaymentIntentSuccess, + InespayPSyncStatus::Failed | InespayPSyncStatus::Rejected => { + Self::PaymentIntentFailure + } + InespayPSyncStatus::Created + | InespayPSyncStatus::Opened + | InespayPSyncStatus::BankSelected + | InespayPSyncStatus::Initiated + | InespayPSyncStatus::Pending + | InespayPSyncStatus::Unfinished + | InespayPSyncStatus::PartiallyAccepted => Self::PaymentIntentProcessing, + InespayPSyncStatus::Aborted + | InespayPSyncStatus::Cancelled + | InespayPSyncStatus::PartRefunded + | InespayPSyncStatus::Refunded => Self::EventNotSupported, + }, + InespayWebhookEventData::Refund(refund_data) => match refund_data.cod_status { + InespayRSyncStatus::Confirmed => Self::RefundSuccess, + InespayRSyncStatus::Rejected + | InespayRSyncStatus::Denied + | InespayRSyncStatus::Reversed + | InespayRSyncStatus::Mistake => Self::RefundFailure, + InespayRSyncStatus::Pending => Self::EventNotSupported, + }, + } + } } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 3ec9d76397b..7b4cbb282df 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1395,10 +1395,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { iatapay::transformers::IatapayAuthType::try_from(self.auth_type)?; Ok(()) } - // api_enums::Connector::Inespay => { - // inespay::transformers::InespayAuthType::try_from(self.auth_type)?; - // Ok(()) - // } + api_enums::Connector::Inespay => { + inespay::transformers::InespayAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Itaubank => { itaubank::transformers::ItaubankAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 114a877d26f..4dc5da6475f 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -442,9 +442,9 @@ impl ConnectorData { enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } - // enums::Connector::Inespay => { - // Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) - // } + enums::Connector::Inespay => { + Ok(ConnectorEnum::Old(Box::new(connector::Inespay::new()))) + } enums::Connector::Itaubank => { Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index c1d9b35be82..2a287a337c9 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -255,7 +255,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, - // api_enums::Connector::Inespay => Self::Inespay, + api_enums::Connector::Inespay => Self::Inespay, api_enums::Connector::Itaubank => Self::Itaubank, api_enums::Connector::Jpmorgan => Self::Jpmorgan, api_enums::Connector::Klarna => Self::Klarna, From b54a3f9142388a3d870406c54fd1d314c7c7748d Mon Sep 17 00:00:00 2001 From: sweta-kumari-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:12:35 +0530 Subject: [PATCH 044/133] FEAT: Add Support for Amazon Pay Redirect and Amazon Pay payment via Stripe (#7056) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 15 +++++++++++++ api-reference/openapi_spec.json | 15 +++++++++++++ crates/api_models/src/payments.rs | 7 ++++++ crates/common_enums/src/enums.rs | 1 + crates/common_enums/src/transformers.rs | 1 + .../connector_configs/toml/development.toml | 2 ++ crates/connector_configs/toml/production.toml | 2 ++ crates/connector_configs/toml/sandbox.toml | 2 ++ crates/euclid/src/frontend/dir/enums.rs | 1 + crates/euclid/src/frontend/dir/lowering.rs | 1 + .../euclid/src/frontend/dir/transformers.rs | 1 + .../src/connectors/airwallex/transformers.rs | 1 + .../connectors/bankofamerica/transformers.rs | 2 ++ .../src/connectors/bluesnap/transformers.rs | 1 + .../src/connectors/boku/transformers.rs | 1 + .../connectors/cybersource/transformers.rs | 2 ++ .../src/connectors/fiuu/transformers.rs | 1 + .../src/connectors/globepay/transformers.rs | 1 + .../connectors/multisafepay/transformers.rs | 3 +++ .../src/connectors/nexinets/transformers.rs | 1 + .../src/connectors/novalnet/transformers.rs | 2 ++ .../src/connectors/shift4/transformers.rs | 1 + .../src/connectors/square/transformers.rs | 1 + .../src/connectors/wellsfargo/transformers.rs | 2 ++ .../src/connectors/worldpay/transformers.rs | 1 + .../src/connectors/zen/transformers.rs | 1 + crates/hyperswitch_connectors/src/utils.rs | 2 ++ .../src/payment_method_data.rs | 9 +++++++- crates/kgraph_utils/src/mca.rs | 1 + crates/kgraph_utils/src/transformers.rs | 1 + crates/openapi/src/openapi.rs | 1 + crates/openapi/src/openapi_v2.rs | 1 + .../payment_connector_required_fields.rs | 15 +++++++++++++ .../router/src/connector/aci/transformers.rs | 1 + crates/router/src/connector/adyen.rs | 3 ++- .../src/connector/adyen/transformers.rs | 1 + .../connector/authorizedotnet/transformers.rs | 1 + .../src/connector/checkout/transformers.rs | 2 ++ crates/router/src/connector/klarna.rs | 2 ++ .../src/connector/mifinity/transformers.rs | 1 + .../router/src/connector/nmi/transformers.rs | 1 + .../router/src/connector/noon/transformers.rs | 1 + .../src/connector/nuvei/transformers.rs | 1 + .../src/connector/payme/transformers.rs | 1 + .../src/connector/paypal/transformers.rs | 2 ++ .../src/connector/stripe/transformers.rs | 22 +++++++++++++++++++ crates/router/src/connector/utils.rs | 2 ++ crates/router/src/core/payments/helpers.rs | 3 ++- crates/router/src/types/transformers.rs | 3 ++- 49 files changed, 142 insertions(+), 4 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 7556a4e7765..a8abc3fb7d0 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -3118,6 +3118,9 @@ "AliPayRedirection": { "type": "object" }, + "AmazonPayRedirectData": { + "type": "object" + }, "AmountDetails": { "type": "object", "required": [ @@ -14273,6 +14276,7 @@ "ali_pay", "ali_pay_hk", "alma", + "amazon_pay", "apple_pay", "atome", "bacs", @@ -20862,6 +20866,17 @@ } } }, + { + "type": "object", + "required": [ + "amazon_pay_redirect" + ], + "properties": { + "amazon_pay_redirect": { + "$ref": "#/components/schemas/AmazonPayRedirectData" + } + } + }, { "type": "object", "required": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index ea0a95c1d57..5bdaa5940c8 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -5882,6 +5882,9 @@ "AliPayRedirection": { "type": "object" }, + "AmazonPayRedirectData": { + "type": "object" + }, "AmountFilter": { "type": "object", "properties": { @@ -17247,6 +17250,7 @@ "ali_pay", "ali_pay_hk", "alma", + "amazon_pay", "apple_pay", "atome", "bacs", @@ -26182,6 +26186,17 @@ } } }, + { + "type": "object", + "required": [ + "amazon_pay_redirect" + ], + "properties": { + "amazon_pay_redirect": { + "$ref": "#/components/schemas/AmazonPayRedirectData" + } + } + }, { "type": "object", "required": [ diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 4a1fa8b408e..ec281197eda 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -2362,6 +2362,7 @@ impl GetPaymentMethodType for WalletData { match self { Self::AliPayQr(_) | Self::AliPayRedirect(_) => api_enums::PaymentMethodType::AliPay, Self::AliPayHkRedirect(_) => api_enums::PaymentMethodType::AliPayHk, + Self::AmazonPayRedirect(_) => api_enums::PaymentMethodType::AmazonPay, Self::MomoRedirect(_) => api_enums::PaymentMethodType::Momo, Self::KakaoPayRedirect(_) => api_enums::PaymentMethodType::KakaoPay, Self::GoPayRedirect(_) => api_enums::PaymentMethodType::GoPay, @@ -3241,6 +3242,8 @@ pub enum WalletData { AliPayRedirect(AliPayRedirection), /// The wallet data for Ali Pay HK redirect AliPayHkRedirect(AliPayHkRedirection), + /// The wallet data for Amazon Pay redirect + AmazonPayRedirect(AmazonPayRedirectData), /// The wallet data for Momo redirect MomoRedirect(MomoRedirection), /// The wallet data for KakaoPay redirect @@ -3324,6 +3327,7 @@ impl GetAddressFromPaymentMethodData for WalletData { | Self::KakaoPayRedirect(_) | Self::GoPayRedirect(_) | Self::GcashRedirect(_) + | Self::AmazonPayRedirect(_) | Self::ApplePay(_) | Self::ApplePayRedirect(_) | Self::ApplePayThirdPartySdk(_) @@ -3481,6 +3485,9 @@ pub struct GooglePayWalletData { #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct ApplePayRedirectData {} +#[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct AmazonPayRedirectData {} + #[derive(Eq, PartialEq, Clone, Debug, serde::Deserialize, serde::Serialize, ToSchema)] pub struct GooglePayRedirectData {} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index d9d76dbc53e..0234fbea6d5 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1557,6 +1557,7 @@ pub enum PaymentMethodType { AliPay, AliPayHk, Alma, + AmazonPay, ApplePay, Atome, Bacs, diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index 7611ae127ee..d351f5c927e 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -1799,6 +1799,7 @@ impl From for PaymentMethod { PaymentMethodType::AliPay => Self::Wallet, PaymentMethodType::AliPayHk => Self::Wallet, PaymentMethodType::Alma => Self::PayLater, + PaymentMethodType::AmazonPay => Self::Wallet, PaymentMethodType::ApplePay => Self::Wallet, PaymentMethodType::Bacs => Self::BankDebit, PaymentMethodType::BancontactCard => Self::BankRedirect, diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 190114fee3a..2b04ea8d1c2 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -3139,6 +3139,8 @@ merchant_secret="Source verification key" payment_method_type = "sepa" [[stripe.bank_transfer]] payment_method_type = "multibanco" +[[stripe.wallet]] + payment_method_type = "amazon_pay" [[stripe.wallet]] payment_method_type = "apple_pay" [[stripe.wallet]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index f1745587a6e..b7108c63477 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -2290,6 +2290,8 @@ merchant_secret="Source verification key" payment_method_type = "bacs" [[stripe.bank_transfer]] payment_method_type = "sepa" +[[stripe.wallet]] + payment_method_type = "amazon_pay" [[stripe.wallet]] payment_method_type = "apple_pay" [[stripe.wallet]] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 2a83d62ee9c..b886e7410a9 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -3081,6 +3081,8 @@ merchant_secret="Source verification key" payment_method_type = "sepa" [[stripe.bank_transfer]] payment_method_type = "multibanco" +[[stripe.wallet]] + payment_method_type = "amazon_pay" [[stripe.wallet]] payment_method_type = "apple_pay" [[stripe.wallet]] diff --git a/crates/euclid/src/frontend/dir/enums.rs b/crates/euclid/src/frontend/dir/enums.rs index 6c96070159b..6fb302641db 100644 --- a/crates/euclid/src/frontend/dir/enums.rs +++ b/crates/euclid/src/frontend/dir/enums.rs @@ -71,6 +71,7 @@ pub enum PayLaterType { #[strum(serialize_all = "snake_case")] pub enum WalletType { GooglePay, + AmazonPay, ApplePay, Paypal, AliPay, diff --git a/crates/euclid/src/frontend/dir/lowering.rs b/crates/euclid/src/frontend/dir/lowering.rs index 07ff2c4364e..04a029bd109 100644 --- a/crates/euclid/src/frontend/dir/lowering.rs +++ b/crates/euclid/src/frontend/dir/lowering.rs @@ -38,6 +38,7 @@ impl From for global_enums::PaymentMethodType { fn from(value: enums::WalletType) -> Self { match value { enums::WalletType::GooglePay => Self::GooglePay, + enums::WalletType::AmazonPay => Self::AmazonPay, enums::WalletType::ApplePay => Self::ApplePay, enums::WalletType::Paypal => Self::Paypal, enums::WalletType::AliPay => Self::AliPay, diff --git a/crates/euclid/src/frontend/dir/transformers.rs b/crates/euclid/src/frontend/dir/transformers.rs index 914a9444918..1a3bb52e0fa 100644 --- a/crates/euclid/src/frontend/dir/transformers.rs +++ b/crates/euclid/src/frontend/dir/transformers.rs @@ -19,6 +19,7 @@ impl IntoDirValue for (global_enums::PaymentMethodType, global_enums::PaymentMet global_enums::PaymentMethodType::AfterpayClearpay => { Ok(dirval!(PayLaterType = AfterpayClearpay)) } + global_enums::PaymentMethodType::AmazonPay => Ok(dirval!(WalletType = AmazonPay)), global_enums::PaymentMethodType::GooglePay => Ok(dirval!(WalletType = GooglePay)), global_enums::PaymentMethodType::ApplePay => Ok(dirval!(WalletType = ApplePay)), global_enums::PaymentMethodType::Paypal => Ok(dirval!(WalletType = Paypal)), diff --git a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs index 45a7b577a86..8c5adefc613 100644 --- a/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/airwallex/transformers.rs @@ -337,6 +337,7 @@ fn get_wallet_details( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index a0868d688eb..11ff53d439c 100644 --- a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -296,6 +296,7 @@ impl TryFrom<&SetupMandateRouterData> for BankOfAmericaPaymentsRequest { WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) @@ -1045,6 +1046,7 @@ impl TryFrom<&BankOfAmericaRouterData<&PaymentsAuthorizeRouterData>> WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs index 7970b1b1d42..809902fa2a2 100644 --- a/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap/transformers.rs @@ -368,6 +368,7 @@ impl TryFrom<&BluesnapRouterData<&types::PaymentsAuthorizeRouterData>> for Blues WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs index da5f775489c..8bc7b3639b7 100644 --- a/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/boku/transformers.rs @@ -180,6 +180,7 @@ fn get_wallet_type(wallet_data: &WalletData) -> Result for CybersourceZeroMandateRequest { WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) @@ -2066,6 +2067,7 @@ impl TryFrom<&CybersourceRouterData<&PaymentsAuthorizeRouterData>> for Cybersour WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 115079c7d00..e23cdd548cb 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -508,6 +508,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs index 5d30146293c..36c83859be9 100644 --- a/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/globepay/transformers.rs @@ -59,6 +59,7 @@ impl TryFrom<&GlobepayRouterData<&types::PaymentsAuthorizeRouterData>> for Globe WalletData::WeChatPayQr(_) => GlobepayChannel::Wechat, WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index a60e8bdc79e..54aca37a76a 100644 --- a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -492,6 +492,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) @@ -556,6 +557,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) @@ -715,6 +717,7 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs index 8d6dc42926b..21fa1ec3217 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexinets/transformers.rs @@ -705,6 +705,7 @@ fn get_wallet_details( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index c1ef6566e03..112d409d6e2 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -341,6 +341,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym WalletDataPaymentMethod::AliPayQr(_) | WalletDataPaymentMethod::AliPayRedirect(_) | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::AmazonPayRedirect(_) | WalletDataPaymentMethod::MomoRedirect(_) | WalletDataPaymentMethod::KakaoPayRedirect(_) | WalletDataPaymentMethod::GoPayRedirect(_) @@ -1586,6 +1587,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { WalletDataPaymentMethod::AliPayQr(_) | WalletDataPaymentMethod::AliPayRedirect(_) | WalletDataPaymentMethod::AliPayHkRedirect(_) + | WalletDataPaymentMethod::AmazonPayRedirect(_) | WalletDataPaymentMethod::MomoRedirect(_) | WalletDataPaymentMethod::KakaoPayRedirect(_) | WalletDataPaymentMethod::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs index 1973e622be1..5c5615aa945 100644 --- a/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/shift4/transformers.rs @@ -286,6 +286,7 @@ impl TryFrom<&WalletData> for Shift4PaymentMethod { fn try_from(wallet_data: &WalletData) -> Result { match wallet_data { WalletData::AliPayRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::ApplePay(_) | WalletData::WeChatPayRedirect(_) | WalletData::AliPayQr(_) diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index ff3999aee64..6c47e5d58d4 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -107,6 +107,7 @@ impl TryFrom<(&types::TokenizationRouterData, WalletData)> for SquareTokenReques | WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index 1a4d3ce8f9c..5eaf54065d7 100644 --- a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -187,6 +187,7 @@ impl TryFrom<&SetupMandateRouterData> for WellsfargoZeroMandateRequest { WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) @@ -1248,6 +1249,7 @@ impl TryFrom<&WellsfargoRouterData<&PaymentsAuthorizeRouterData>> for Wellsfargo WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index e4656021a59..47a5d3b2b2b 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -156,6 +156,7 @@ fn fetch_payment_instrument( WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index cc6795f157a..a9e21f9071c 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -487,6 +487,7 @@ impl | WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) | WalletData::MomoRedirect(_) | WalletData::KakaoPayRedirect(_) | WalletData::GoPayRedirect(_) diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index c69641ebd11..418c505cf52 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2296,6 +2296,7 @@ pub enum PaymentMethodDataType { AliPayQr, AliPayRedirect, AliPayHkRedirect, + AmazonPayRedirect, MomoRedirect, KakaoPayRedirect, GoPayRedirect, @@ -2415,6 +2416,7 @@ impl From for PaymentMethodDataType { payment_method_data::WalletData::AliPayQr(_) => Self::AliPayQr, payment_method_data::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, payment_method_data::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, + payment_method_data::WalletData::AmazonPayRedirect(_) => Self::AmazonPayRedirect, payment_method_data::WalletData::MomoRedirect(_) => Self::MomoRedirect, payment_method_data::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, payment_method_data::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index dcf46964170..9e83810c9e0 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1,6 +1,8 @@ use api_models::{ mandates, payment_methods, - payments::{additional_info as payment_additional_types, ExtendedCardInfo}, + payments::{ + additional_info as payment_additional_types, AmazonPayRedirectData, ExtendedCardInfo, + }, }; use common_enums::enums as api_enums; use common_utils::{ @@ -169,6 +171,7 @@ pub enum WalletData { AliPayQr(Box), AliPayRedirect(AliPayRedirection), AliPayHkRedirect(AliPayHkRedirection), + AmazonPayRedirect(Box), MomoRedirect(MomoRedirection), KakaoPayRedirect(KakaoPayRedirection), GoPayRedirect(GoPayRedirection), @@ -729,6 +732,9 @@ impl From for WalletData { api_models::payments::WalletData::AliPayHkRedirect(_) => { Self::AliPayHkRedirect(AliPayHkRedirection {}) } + api_models::payments::WalletData::AmazonPayRedirect(_) => { + Self::AmazonPayRedirect(Box::new(AmazonPayRedirectData {})) + } api_models::payments::WalletData::MomoRedirect(_) => { Self::MomoRedirect(MomoRedirection {}) } @@ -1518,6 +1524,7 @@ impl GetPaymentMethodType for WalletData { match self { Self::AliPayQr(_) | Self::AliPayRedirect(_) => api_enums::PaymentMethodType::AliPay, Self::AliPayHkRedirect(_) => api_enums::PaymentMethodType::AliPayHk, + Self::AmazonPayRedirect(_) => api_enums::PaymentMethodType::AmazonPay, Self::MomoRedirect(_) => api_enums::PaymentMethodType::Momo, Self::KakaoPayRedirect(_) => api_enums::PaymentMethodType::KakaoPay, Self::GoPayRedirect(_) => api_enums::PaymentMethodType::GoPay, diff --git a/crates/kgraph_utils/src/mca.rs b/crates/kgraph_utils/src/mca.rs index a3e1dfc6220..e224078493f 100644 --- a/crates/kgraph_utils/src/mca.rs +++ b/crates/kgraph_utils/src/mca.rs @@ -21,6 +21,7 @@ fn get_dir_value_payment_method( from: api_enums::PaymentMethodType, ) -> Result { match from { + api_enums::PaymentMethodType::AmazonPay => Ok(dirval!(WalletType = AmazonPay)), api_enums::PaymentMethodType::Credit => Ok(dirval!(CardType = Credit)), api_enums::PaymentMethodType::Debit => Ok(dirval!(CardType = Debit)), api_enums::PaymentMethodType::Giropay => Ok(dirval!(BankRedirectType = Giropay)), diff --git a/crates/kgraph_utils/src/transformers.rs b/crates/kgraph_utils/src/transformers.rs index 89b8c5a34ad..adfe5820866 100644 --- a/crates/kgraph_utils/src/transformers.rs +++ b/crates/kgraph_utils/src/transformers.rs @@ -130,6 +130,7 @@ impl IntoDirValue for api_enums::FutureUsage { impl IntoDirValue for (api_enums::PaymentMethodType, api_enums::PaymentMethod) { fn into_dir_value(self) -> Result { match self.0 { + api_enums::PaymentMethodType::AmazonPay => Ok(dirval!(WalletType = AmazonPay)), api_enums::PaymentMethodType::Credit => Ok(dirval!(CardType = Credit)), api_enums::PaymentMethodType::Debit => Ok(dirval!(CardType = Debit)), api_enums::PaymentMethodType::Giropay => Ok(dirval!(BankRedirectType = Giropay)), diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index de358f79332..7102c23d17e 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -479,6 +479,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AchTransfer, api_models::payments::MultibancoTransferInstructions, api_models::payments::DokuBankTransferInstructions, + api_models::payments::AmazonPayRedirectData, api_models::payments::ApplePayRedirectData, api_models::payments::ApplePayThirdPartySdkData, api_models::payments::GooglePayRedirectData, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 1078d8080ca..ac88908fbd2 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -440,6 +440,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AchTransfer, api_models::payments::MultibancoTransferInstructions, api_models::payments::DokuBankTransferInstructions, + api_models::payments::AmazonPayRedirectData, api_models::payments::ApplePayRedirectData, api_models::payments::ApplePayThirdPartySdkData, api_models::payments::GooglePayRedirectData, diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 644c213acd8..421f430e74e 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -9488,6 +9488,21 @@ impl Default for settings::RequiredFields { ]), }, ), + ( + enums::PaymentMethodType::AmazonPay, + ConnectorFields { + fields: HashMap::from([ + ( + enums::Connector::Stripe, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), + ]), + }, + ), ( enums::PaymentMethodType::Cashapp, ConnectorFields { diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/router/src/connector/aci/transformers.rs index 3e25799c02e..11d34e55b7b 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/router/src/connector/aci/transformers.rs @@ -108,6 +108,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> for Pay account_id: None, })), domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index d6f6fdc8f0f..91e65759573 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -215,7 +215,8 @@ impl ConnectorValidation for Adyen { ) } }, - PaymentMethodType::CardRedirect + PaymentMethodType::AmazonPay + | PaymentMethodType::CardRedirect | PaymentMethodType::DirectCarrierBilling | PaymentMethodType::Fps | PaymentMethodType::DuitNow diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 394652c33e1..adb1a795f70 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2211,6 +2211,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> domain::WalletData::DanaRedirect { .. } => Ok(AdyenPaymentMethod::Dana), domain::WalletData::SwishQr(_) => Ok(AdyenPaymentMethod::Swish), domain::WalletData::AliPayQr(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::ApplePayRedirect(_) | domain::WalletData::ApplePayThirdPartySdk(_) | domain::WalletData::GooglePayRedirect(_) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index c430191a1ec..276ea6bf77b 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -1809,6 +1809,7 @@ fn get_wallet_data( domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index bb38ef74836..321a8cec765 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -93,6 +93,7 @@ impl TryFrom<&types::TokenizationRouterData> for TokenRequest { domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) @@ -347,6 +348,7 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 4e54311a89a..6fbc48a66a0 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -577,6 +577,7 @@ impl | common_enums::PaymentMethodType::AliPay | common_enums::PaymentMethodType::AliPayHk | common_enums::PaymentMethodType::Alma + | common_enums::PaymentMethodType::AmazonPay | common_enums::PaymentMethodType::ApplePay | common_enums::PaymentMethodType::Atome | common_enums::PaymentMethodType::Bacs @@ -693,6 +694,7 @@ impl | common_enums::PaymentMethodType::AliPay | common_enums::PaymentMethodType::AliPayHk | common_enums::PaymentMethodType::Alma + | common_enums::PaymentMethodType::AmazonPay | common_enums::PaymentMethodType::ApplePay | common_enums::PaymentMethodType::Atome | common_enums::PaymentMethodType::Bacs diff --git a/crates/router/src/connector/mifinity/transformers.rs b/crates/router/src/connector/mifinity/transformers.rs index 1e2c420c767..f913077d1dc 100644 --- a/crates/router/src/connector/mifinity/transformers.rs +++ b/crates/router/src/connector/mifinity/transformers.rs @@ -154,6 +154,7 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 4c33f020017..19f03cb1b2c 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -545,6 +545,7 @@ impl domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/noon/transformers.rs b/crates/router/src/connector/noon/transformers.rs index b96283247b4..5816aaa9295 100644 --- a/crates/router/src/connector/noon/transformers.rs +++ b/crates/router/src/connector/noon/transformers.rs @@ -314,6 +314,7 @@ impl TryFrom<&NoonRouterData<&types::PaymentsAuthorizeRouterData>> for NoonPayme domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index c98a0647778..eea700f695c 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -902,6 +902,7 @@ where domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index 7e50f7f40e6..c13730529e2 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -393,6 +393,7 @@ impl TryFrom<&PaymentMethodData> for SalePaymentMethod { domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 8f739792a0c..18739851ae8 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -1044,6 +1044,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP domain::WalletData::AliPayQr(_) | domain::WalletData::AliPayRedirect(_) | domain::WalletData::AliPayHkRedirect(_) + | domain::WalletData::AmazonPayRedirect(_) | domain::WalletData::MomoRedirect(_) | domain::WalletData::KakaoPayRedirect(_) | domain::WalletData::GoPayRedirect(_) @@ -1134,6 +1135,7 @@ impl TryFrom<&PaypalRouterData<&types::PaymentsAuthorizeRouterData>> for PaypalP | enums::PaymentMethodType::AliPay | enums::PaymentMethodType::AliPayHk | enums::PaymentMethodType::Alma + | enums::PaymentMethodType::AmazonPay | enums::PaymentMethodType::ApplePay | enums::PaymentMethodType::Atome | enums::PaymentMethodType::Bacs diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 60852779884..363dfb81bcc 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -531,6 +531,7 @@ pub enum StripeWallet { ApplepayToken(StripeApplePay), GooglepayToken(GooglePayToken), ApplepayPayment(ApplepayPayment), + AmazonpayPayment(AmazonpayPayment), WechatpayPayment(WechatpayPayment), AlipayPayment(AlipayPayment), Cashapp(CashappPayment), @@ -577,6 +578,12 @@ pub struct ApplepayPayment { pub payment_method_types: StripePaymentMethodType, } +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct AmazonpayPayment { + #[serde(rename = "payment_method_data[type]")] + pub payment_method_types: StripePaymentMethodType, +} + #[derive(Debug, Eq, PartialEq, Serialize)] pub struct AlipayPayment { #[serde(rename = "payment_method_data[type]")] @@ -620,6 +627,8 @@ pub enum StripePaymentMethodType { Affirm, AfterpayClearpay, Alipay, + #[serde(rename = "amazon_pay")] + AmazonPay, #[serde(rename = "au_becs_debit")] Becs, #[serde(rename = "bacs_debit")] @@ -667,6 +676,7 @@ impl TryFrom for StripePaymentMethodType { enums::PaymentMethodType::Giropay => Ok(Self::Giropay), enums::PaymentMethodType::Ideal => Ok(Self::Ideal), enums::PaymentMethodType::Sofort => Ok(Self::Sofort), + enums::PaymentMethodType::AmazonPay => Ok(Self::AmazonPay), enums::PaymentMethodType::ApplePay => Ok(Self::Card), enums::PaymentMethodType::Ach => Ok(Self::Ach), enums::PaymentMethodType::Sepa => Ok(Self::Sepa), @@ -1048,6 +1058,9 @@ impl ForeignTryFrom<&domain::WalletData> for Option { domain::WalletData::GooglePay(_) => Ok(Some(StripePaymentMethodType::Card)), domain::WalletData::WeChatPayQr(_) => Ok(Some(StripePaymentMethodType::Wechatpay)), domain::WalletData::CashappQr(_) => Ok(Some(StripePaymentMethodType::Cashapp)), + domain::WalletData::AmazonPayRedirect(_) => { + Ok(Some(StripePaymentMethodType::AmazonPay)) + } domain::WalletData::MobilePayRedirect(_) => { Err(errors::ConnectorError::NotImplemented( connector_util::get_unimplemented_payment_method_error_message("stripe"), @@ -1466,6 +1479,11 @@ impl TryFrom<(&domain::WalletData, Option)> for Strip payment_method_data_type: StripePaymentMethodType::Cashapp, }))) } + domain::WalletData::AmazonPayRedirect(_) => Ok(Self::Wallet( + StripeWallet::AmazonpayPayment(AmazonpayPayment { + payment_method_types: StripePaymentMethodType::AmazonPay, + }), + )), domain::WalletData::GooglePay(gpay_data) => Ok(Self::try_from(gpay_data)?), domain::WalletData::PaypalRedirect(_) | domain::WalletData::MobilePayRedirect(_) => { Err(errors::ConnectorError::NotImplemented( @@ -2301,6 +2319,7 @@ pub enum StripePaymentMethodDetailsResponse { Klarna, Affirm, AfterpayClearpay, + AmazonPay, ApplePay, #[serde(rename = "us_bank_account")] Ach, @@ -2348,6 +2367,7 @@ impl StripePaymentMethodDetailsResponse { | Self::Klarna | Self::Affirm | Self::AfterpayClearpay + | Self::AmazonPay | Self::ApplePay | Self::Ach | Self::Sepa @@ -2664,6 +2684,7 @@ impl | Some(StripePaymentMethodDetailsResponse::Klarna) | Some(StripePaymentMethodDetailsResponse::Affirm) | Some(StripePaymentMethodDetailsResponse::AfterpayClearpay) + | Some(StripePaymentMethodDetailsResponse::AmazonPay) | Some(StripePaymentMethodDetailsResponse::ApplePay) | Some(StripePaymentMethodDetailsResponse::Ach) | Some(StripePaymentMethodDetailsResponse::Sepa) @@ -3273,6 +3294,7 @@ pub enum StripePaymentMethodOptions { Klarna {}, Affirm {}, AfterpayClearpay {}, + AmazonPay {}, Eps {}, Giropay {}, Ideal {}, diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index cbf1867b08b..c4824d65836 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2744,6 +2744,7 @@ pub enum PaymentMethodDataType { AliPayQr, AliPayRedirect, AliPayHkRedirect, + AmazonPayRedirect, MomoRedirect, KakaoPayRedirect, GoPayRedirect, @@ -2862,6 +2863,7 @@ impl From for PaymentMethodDataType { domain::payments::WalletData::AliPayQr(_) => Self::AliPayQr, domain::payments::WalletData::AliPayRedirect(_) => Self::AliPayRedirect, domain::payments::WalletData::AliPayHkRedirect(_) => Self::AliPayHkRedirect, + domain::payments::WalletData::AmazonPayRedirect(_) => Self::AmazonPayRedirect, domain::payments::WalletData::MomoRedirect(_) => Self::MomoRedirect, domain::payments::WalletData::KakaoPayRedirect(_) => Self::KakaoPayRedirect, domain::payments::WalletData::GoPayRedirect(_) => Self::GoPayRedirect, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index b0df22f3d04..db12a6282c3 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -2740,7 +2740,8 @@ pub fn validate_payment_method_type_against_payment_method( ), api_enums::PaymentMethod::Wallet => matches!( payment_method_type, - api_enums::PaymentMethodType::ApplePay + api_enums::PaymentMethodType::AmazonPay + | api_enums::PaymentMethodType::ApplePay | api_enums::PaymentMethodType::GooglePay | api_enums::PaymentMethodType::Paypal | api_enums::PaymentMethodType::AliPay diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 2a287a337c9..c8222163b52 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -453,7 +453,8 @@ impl ForeignFrom for Option { impl ForeignFrom for api_enums::PaymentMethod { fn foreign_from(payment_method_type: api_enums::PaymentMethodType) -> Self { match payment_method_type { - api_enums::PaymentMethodType::ApplePay + api_enums::PaymentMethodType::AmazonPay + | api_enums::PaymentMethodType::ApplePay | api_enums::PaymentMethodType::GooglePay | api_enums::PaymentMethodType::Paypal | api_enums::PaymentMethodType::AliPay From 7ea630da002fcb3f8ab9093114efe7973b1d347d Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:14:16 +0530 Subject: [PATCH 045/133] feat(core): Add Authorize flow as fallback flow while fetching GSM for refund errors (#7129) --- crates/router/src/consts.rs | 3 +++ crates/router/src/core/refunds.rs | 15 +++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 585e894adf9..d27ad5d6019 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -215,3 +215,6 @@ pub const AUTHENTICATION_SERVICE_ELIGIBLE_CONFIG: &str = /// Refund flow identifier used for performing GSM operations pub const REFUND_FLOW_STR: &str = "refund_flow"; + +/// Authorize flow identifier used for performing GSM operations +pub const AUTHORIZE_FLOW_STR: &str = "Authorize"; diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 1e788b6acee..a6505f23631 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -297,6 +297,21 @@ pub async fn trigger_refund_to_gateway( consts::REFUND_FLOW_STR.to_string(), ) .await; + // Note: Some connectors do not have a separate list of refund errors + // In such cases, the error codes and messages are stored under "Authorize" flow in GSM table + // So we will have to fetch the GSM using Authorize flow in case GSM is not found using "refund_flow" + let option_gsm = if option_gsm.is_none() { + helpers::get_gsm_record( + state, + Some(err.code.clone()), + Some(err.message.clone()), + connector.connector_name.to_string(), + consts::AUTHORIZE_FLOW_STR.to_string(), + ) + .await + } else { + option_gsm + }; let gsm_unified_code = option_gsm.as_ref().and_then(|gsm| gsm.unified_code.clone()); let gsm_unified_message = option_gsm.and_then(|gsm| gsm.unified_message); From 22072fd750940ac7fec6ea971737409518600891 Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Wed, 5 Feb 2025 19:14:56 +0530 Subject: [PATCH 046/133] feat(connector): [Deutschebank] Add Access Token Error struct (#7127) --- config/config.example.toml | 3 ++ config/docker_compose.toml | 3 ++ .../src/connectors/deutschebank.rs | 35 +++++++++++++++---- .../connectors/deutschebank/transformers.rs | 17 +++++++-- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index c113d7abd57..e982a01eb11 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -585,6 +585,9 @@ paypal = { country = "AD,AE,AL,AM,AR,AT,AU,AZ,BA,BB,BD,BE,BG,BH,BI,BM,BN,BO,BR,B [pm_filters.mifinity] mifinity = { country = "BR,CN,SG,MY,DE,CH,DK,GB,ES,AD,GI,FI,FR,GR,HR,IT,JP,MX,AR,CO,CL,PE,VE,UY,PY,BO,EC,GT,HN,SV,NI,CR,PA,DO,CU,PR,NL,NO,PL,PT,SE,RU,TR,TW,HK,MO,AX,AL,DZ,AS,AO,AI,AG,AM,AW,AU,AT,AZ,BS,BH,BD,BB,BE,BZ,BJ,BM,BT,BQ,BA,BW,IO,BN,BG,BF,BI,KH,CM,CA,CV,KY,CF,TD,CX,CC,KM,CG,CK,CI,CW,CY,CZ,DJ,DM,EG,GQ,ER,EE,ET,FK,FO,FJ,GF,PF,TF,GA,GM,GE,GH,GL,GD,GP,GU,GG,GN,GW,GY,HT,HM,VA,IS,IN,ID,IE,IM,IL,JE,JO,KZ,KE,KI,KW,KG,LA,LV,LB,LS,LI,LT,LU,MK,MG,MW,MV,ML,MT,MH,MQ,MR,MU,YT,FM,MD,MC,MN,ME,MS,MA,MZ,NA,NR,NP,NC,NZ,NE,NG,NU,NF,MP,OM,PK,PW,PS,PG,PH,PN,QA,RE,RO,RW,BL,SH,KN,LC,MF,PM,VC,WS,SM,ST,SA,SN,RS,SC,SL,SX,SK,SI,SB,SO,ZA,GS,KR,LK,SR,SJ,SZ,TH,TL,TG,TK,TO,TT,TN,TM,TC,TV,UG,UA,AE,UZ,VU,VN,VG,VI,WF,EH,ZM", currency = "AUD,CAD,CHF,CNY,CZK,DKK,EUR,GBP,INR,JPY,NOK,NZD,PLN,RUB,SEK,ZAR,USD,EGP,UYU,UZS" } +[pm_filters.fiuu] +duit_now = { country = "MY", currency = "MYR" } + [connector_customer] connector_list = "gocardless,stax,stripe" payout_connector_list = "stripe,wise" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 0d3cbb4f6af..12b8df6683c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -508,6 +508,9 @@ credit = { country = "AF,DZ,AW,AU,AZ,BS,BH,BD,BB,BZ,BM,BT,BO,BA,BW,BR,BN,BG,BI,K google_pay = { country = "AL,DZ,AS,AO,AG,AR,AU,AT,AZ,BH,BY,BE,BR,BG,CA,CL,CO,HR,CZ,DK,DO,EG,EE,FI,FR,DE,GR,HK,HU,IN,ID,IE,IL,IT,JP,JO,KZ,KE,KW,LV,LB,LT,LU,MY,MX,NL,NZ,NO,OM,PK,PA,PE,PH,PL,PT,QA,RO,RU,SA,SG,SK,ZA,ES,LK,SE,CH,TW,TH,TR,UA,AE,GB,US,UY,VN" } apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US" } +[pm_filters.fiuu] +duit_now = { country = "MY", currency = "MYR" } + [bank_config.online_banking_fpx] adyen.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" fiuu.banks = "affin_bank,agro_bank,alliance_bank,am_bank,bank_of_china,bank_islam,bank_muamalat,bank_rakyat,bank_simpanan_nasional,cimb_bank,hong_leong_bank,hsbc_bank,kuwait_finance_house,maybank,ocbc_bank,public_bank,rhb_bank,standard_chartered_bank,uob_bank" diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index d7dcb63c476..0b6b2f132e7 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -159,9 +159,9 @@ impl ConnectorCommon for Deutschebank { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: deutschebank::DeutschebankErrorResponse = res + let response: deutschebank::PaymentsErrorResponse = res .response - .parse_struct("DeutschebankErrorResponse") + .parse_struct("PaymentsErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_response_body(&response)); @@ -289,7 +289,33 @@ impl ConnectorIntegration res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - self.build_error_response(res, event_builder) + let response: deutschebank::DeutschebankError = res + .response + .parse_struct("DeutschebankError") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + match response { + deutschebank::DeutschebankError::PaymentsErrorResponse(response) => Ok(ErrorResponse { + status_code: res.status_code, + code: response.rc, + message: response.message.clone(), + reason: Some(response.message), + attempt_status: None, + connector_transaction_id: None, + }), + deutschebank::DeutschebankError::AccessTokenErrorResponse(response) => { + Ok(ErrorResponse { + status_code: res.status_code, + code: response.cause.clone(), + message: response.cause.clone(), + reason: Some(response.description), + attempt_status: None, + connector_transaction_id: None, + }) + } + } } } @@ -912,9 +938,6 @@ impl ConnectorIntegration for Deutscheb .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) .build(), )) } diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index 8133be2fa79..b026c339e2a 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -1224,8 +1224,21 @@ impl TryFrom> } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct DeutschebankErrorResponse { +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct PaymentsErrorResponse { pub rc: String, pub message: String, } + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq, Clone)] +pub struct AccessTokenErrorResponse { + pub cause: String, + pub description: String, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(untagged)] +pub enum DeutschebankError { + PaymentsErrorResponse(PaymentsErrorResponse), + AccessTokenErrorResponse(AccessTokenErrorResponse), +} From 155917898cc443edc713513ea1376f045dfc0739 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:26:54 +0000 Subject: [PATCH 047/133] chore(postman): update Postman collection files --- .../adyen_uk.postman_collection.json | 441 +----------------- 1 file changed, 10 insertions(+), 431 deletions(-) diff --git a/postman/collection-json/adyen_uk.postman_collection.json b/postman/collection-json/adyen_uk.postman_collection.json index ebf353bb88d..071c3ca52c1 100644 --- a/postman/collection-json/adyen_uk.postman_collection.json +++ b/postman/collection-json/adyen_uk.postman_collection.json @@ -5367,428 +5367,7 @@ ] }, { - "name": "Scenario11-Bank Redirect-sofort", - "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 \"requires_payment_method\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments - Content check if value for 'status' matches 'requires_payment_method'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_payment_method\");", - " },", - " );", - "}", - "" - ], - "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\":1000,\"currency\":\"EUR\",\"confirm\":false,\"capture_method\":\"automatic\",\"capture_on\":\"2022-09-10T10:11:12Z\",\"amount_to_capture\":1000,\"customer_id\":\"StripeCustomer\",\"email\":\"abcdef123@gmail.com\",\"name\":\"John Doe\",\"phone\":\"999999999\",\"phone_country_code\":\"+65\",\"description\":\"Its my first payment request\",\"authentication_type\":\"three_ds\",\"return_url\":\"https://duck.com\",\"billing\":{\"address\":{\"first_name\":\"John\",\"last_name\":\"Doe\",\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"DE\"}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"},\"shipping\":{\"address\":{\"line1\":\"1467\",\"line2\":\"Harrison Street\",\"line3\":\"Harrison Street\",\"city\":\"San Fransico\",\"state\":\"California\",\"zip\":\"94122\",\"country\":\"US\",\"first_name\":\"John\",\"last_name\":\"Doe\"}},\"statement_descriptor_name\":\"joseph\",\"statement_descriptor_suffix\":\"JS\",\"metadata\":{\"udf1\":\"value1\",\"new_customer\":\"true\",\"login_date\":\"2019-09-10T10:11:12Z\"}}" - }, - "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 - Confirm", - "event": [ - { - "listen": "test", - "script": { - "exec": [ - "// Validate status 2xx", - "pm.test(\"[POST]::/payments/:id/confirm - Status code is 2xx\", function () {", - " pm.response.to.be.success;", - "});", - "", - "// Validate if response header has matching content-type", - "pm.test(", - " \"[POST]::/payments/:id/confirm - 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/:id/confirm - 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 \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", - "", - "// Response body should have \"next_action.redirect_to_url\"", - "pm.test(", - " \"[POST]::/payments - Content check if 'next_action.redirect_to_url' exists\",", - " function () {", - " pm.expect(typeof jsonData.next_action.redirect_to_url !== \"undefined\").to.be", - " .true;", - " },", - ");", - "", - "// Response body should have value \"sofort\" for \"payment_method_type\"", - "if (jsonData?.payment_method_type) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'payment_method_type' matches 'sofort'\",", - " function () {", - " pm.expect(jsonData.payment_method_type).to.eql(\"sofort\");", - " },", - " );", - "}", - "", - "// Response body should have value \"stripe\" for \"connector\"", - "if (jsonData?.connector) {", - " pm.test(", - " \"[POST]::/payments/:id/confirm - Content check if value for 'connector' matches 'adyen'\",", - " function () {", - " pm.expect(jsonData.connector).to.eql(\"adyen\");", - " },", - " );", - "}", - "" - ], - "type": "text/javascript" - } - } - ], - "request": { - "auth": { - "type": "apikey", - "apikey": [ - { - "key": "value", - "value": "{{publishable_key}}", - "type": "string" - }, - { - "key": "key", - "value": "api-key", - "type": "string" - }, - { - "key": "in", - "value": "header", - "type": "string" - } - ] - }, - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - }, - { - "key": "Accept", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "options": { - "raw": { - "language": "json" - } - }, - "raw": "{\"client_secret\":\"{{client_secret}}\",\"payment_method\":\"bank_redirect\",\"payment_method_type\":\"sofort\",\"payment_method_data\":{\"bank_redirect\":{\"sofort\":{\"billing_details\":{\"billing_name\":\"John Doe\"},\"bank_name\":\"ing\",\"preferred_language\":\"en\",\"country\":\"DE\"}}},\"browser_info\":{\"user_agent\":\"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36\",\"accept_header\":\"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\",\"language\":\"nl-NL\",\"color_depth\":24,\"screen_height\":723,\"screen_width\":1536,\"time_zone\":0,\"java_enabled\":true,\"java_script_enabled\":true,\"ip_address\":\"127.0.0.1\"}}" - }, - "url": { - "raw": "{{baseUrl}}/payments/:id/confirm", - "host": [ - "{{baseUrl}}" - ], - "path": [ - "payments", - ":id", - "confirm" - ], - "variable": [ - { - "key": "id", - "value": "{{payment_id}}", - "description": "(Required) unique payment id" - } - ] - }, - "description": "This API is to confirm the payment request and forward payment to the payment processor. This API provides more granular control upon when the API is forwarded to the payment processor. Alternatively you can confirm the payment within the Payments-Create API" - }, - "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 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 \"requires_customer_action\" for \"status\"", - "if (jsonData?.status) {", - " pm.test(", - " \"[POST]::/payments:id - Content check if value for 'status' matches 'requires_customer_action'\",", - " function () {", - " pm.expect(jsonData.status).to.eql(\"requires_customer_action\");", - " },", - " );", - "}", - "" - ], - "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": "Scenario12-Bank Redirect-eps", + "name": "Scenario11-Bank Redirect-eps", "item": [ { "name": "Payments - Create", @@ -6209,7 +5788,7 @@ ] }, { - "name": "Scenario13-Refund recurring payment", + "name": "Scenario12-Refund recurring payment", "item": [ { "name": "Payments - Create", @@ -6969,7 +6548,7 @@ ] }, { - "name": "Scenario14-Bank debit-ach", + "name": "Scenario13-Bank debit-ach", "item": [ { "name": "Payments - Create", @@ -7381,7 +6960,7 @@ ] }, { - "name": "Scenario15-Bank debit-Bacs", + "name": "Scenario14-Bank debit-Bacs", "item": [ { "name": "Payments - Create", @@ -7793,7 +7372,7 @@ ] }, { - "name": "Scenario16-Bank Redirect-Trustly", + "name": "Scenario15-Bank Redirect-Trustly", "item": [ { "name": "Payments - Create", @@ -8214,7 +7793,7 @@ ] }, { - "name": "Scenario17-Add card flow", + "name": "Scenario16-Add card flow", "item": [ { "name": "Payments - Create", @@ -9024,7 +8603,7 @@ ] }, { - "name": "Scenario18-Pass Invalid CVV for save card flow and verify failed payment", + "name": "Scenario17-Pass Invalid CVV for save card flow and verify failed payment", "item": [ { "name": "Payments - Create", @@ -9667,7 +9246,7 @@ ] }, { - "name": "Scenario19-Don't Pass CVV for save card flow and verify failed payment Copy", + "name": "Scenario18-Don't Pass CVV for save card flow and verify failed payment Copy", "item": [ { "name": "Payments - Create", @@ -10310,7 +9889,7 @@ ] }, { - "name": "Scenario20-Create Gift Card payment", + "name": "Scenario19-Create Gift Card payment", "item": [ { "name": "Payments - Create", @@ -14375,4 +13954,4 @@ "type": "string" } ] -} \ No newline at end of file +} From 3da637e6960f73a3a69aef98b9de2e6de01fcb5e Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 6 Feb 2025 00:31:16 +0000 Subject: [PATCH 048/133] chore(version): 2025.02.06.0 --- CHANGELOG.md | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6cc3a151af..57524ca4ddd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.06.0 + +### Features + +- **analytics:** Add currency as dimension and filter for disputes ([#7006](https://github.com/juspay/hyperswitch/pull/7006)) ([`12a2f2a`](https://github.com/juspay/hyperswitch/commit/12a2f2ad147346365f828d8fc97eb9fe49a845bb)) +- **connector:** + - [INESPAY] Integrate Sepa Bank Debit ([#6755](https://github.com/juspay/hyperswitch/pull/6755)) ([`ce2485c`](https://github.com/juspay/hyperswitch/commit/ce2485c3c77d86a2bce01d20c410ae11ac08c555)) + - [Deutschebank] Add Access Token Error struct ([#7127](https://github.com/juspay/hyperswitch/pull/7127)) ([`22072fd`](https://github.com/juspay/hyperswitch/commit/22072fd750940ac7fec6ea971737409518600891)) +- **core:** + - Google pay decrypt flow ([#6991](https://github.com/juspay/hyperswitch/pull/6991)) ([`e0ec27d`](https://github.com/juspay/hyperswitch/commit/e0ec27d936fc62a6feb2f8f643a218f3ad7483b5)) + - Implement 3ds decision manger for V2 ([#7022](https://github.com/juspay/hyperswitch/pull/7022)) ([`1900959`](https://github.com/juspay/hyperswitch/commit/190095977819efac42da5483bfdae6420a7a402c)) + - Add Authorize flow as fallback flow while fetching GSM for refund errors ([#7129](https://github.com/juspay/hyperswitch/pull/7129)) ([`7ea630d`](https://github.com/juspay/hyperswitch/commit/7ea630da002fcb3f8ab9093114efe7973b1d347d)) +- **payments_v2:** Implement create and confirm intent flow ([#7106](https://github.com/juspay/hyperswitch/pull/7106)) ([`67ea754`](https://github.com/juspay/hyperswitch/commit/67ea754e383d2f9539d16f7fa40f201f177b5ea3)) +- **users:** Custom role at profile read ([#6875](https://github.com/juspay/hyperswitch/pull/6875)) ([`899c207`](https://github.com/juspay/hyperswitch/commit/899c207d5835ba39f5163d12c6f59aed39884359)) +- Add Support for Amazon Pay Redirect and Amazon Pay payment via Stripe ([#7056](https://github.com/juspay/hyperswitch/pull/7056)) ([`b54a3f9`](https://github.com/juspay/hyperswitch/commit/b54a3f9142388a3d870406c54fd1d314c7c7748d)) + +### Bug Fixes + +- **connector:** + - [BOA] throw unsupported error incase of 3DS cards and limit administrative area length to 20 characters ([#7174](https://github.com/juspay/hyperswitch/pull/7174)) ([`6f90b93`](https://github.com/juspay/hyperswitch/commit/6f90b93cee6eb5fb688750b940ea884af8b1caa3)) + - [Deutschebank] Display deutschebank card payment method in dashboard ([#7060](https://github.com/juspay/hyperswitch/pull/7060)) ([`f71cc96`](https://github.com/juspay/hyperswitch/commit/f71cc96a33ee3a9babb334c068dce7fbb3063e25)) + - [Authorizedotnet] fix deserialization error for Paypal while canceling payment ([#7141](https://github.com/juspay/hyperswitch/pull/7141)) ([`698a0aa`](https://github.com/juspay/hyperswitch/commit/698a0aa75af646107ac796f719b51e74530f11dc)) + - [worldpay] remove threeDS data from Authorize request for NTI flows ([#7097](https://github.com/juspay/hyperswitch/pull/7097)) ([`d443a4c`](https://github.com/juspay/hyperswitch/commit/d443a4cf1ee7bb9f5daa5147bd2854b3e4f4c76d)) +- **core:** Add payment_link_data in PaymentData for Psync ([#7137](https://github.com/juspay/hyperswitch/pull/7137)) ([`8917235`](https://github.com/juspay/hyperswitch/commit/8917235b4c1c606cba92539b9cb50449fc70474a)) + +### Refactors + +- **ci:** Remove Adyen-specific deprecated PMTs Sofort test cases in Postman ([#7099](https://github.com/juspay/hyperswitch/pull/7099)) ([`6fee301`](https://github.com/juspay/hyperswitch/commit/6fee3011ea84e08caef8459cd1f55856245e15b2)) +- **connector:** [AUTHORIZEDOTNET] Add metadata information to connector request ([#7011](https://github.com/juspay/hyperswitch/pull/7011)) ([`ea18886`](https://github.com/juspay/hyperswitch/commit/ea1888677df7de60a248184389d7be30ae21fc59)) +- **core:** Add recurring customer support for nomupay payouts. ([#6687](https://github.com/juspay/hyperswitch/pull/6687)) ([`8d8ebe9`](https://github.com/juspay/hyperswitch/commit/8d8ebe9051675d8102c6f9ea887bb23751ea5724)) + +### Miscellaneous Tasks + +- **postman:** Update Postman collection files ([`1559178`](https://github.com/juspay/hyperswitch/commit/155917898cc443edc713513ea1376f045dfc0739)) + +### Build System / Dependencies + +- **deps:** Bump `openssl` from 0.10.66 to 0.10.70 ([#7187](https://github.com/juspay/hyperswitch/pull/7187)) ([`91626c0`](https://github.com/juspay/hyperswitch/commit/91626c0c2554126a37f2624d3b0e2b2b60be3849)) + +**Full Changelog:** [`2025.02.05.0...2025.02.06.0`](https://github.com/juspay/hyperswitch/compare/2025.02.05.0...2025.02.06.0) + +- - - + ## 2025.02.05.0 ### Features From d5cbc1d46c79ada41d2cc7bef5ce6411a67e1136 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Thu, 6 Feb 2025 14:57:37 +0530 Subject: [PATCH 049/133] ci(cypress): fix nmi and paypal (#7173) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .github/workflows/cypress-tests-runner.yml | 2 +- .../cypress/e2e/configs/Payment/Utils.js | 27 +++++++- .../00020-MandatesUsingNTIDProxy.cy.js | 66 +++++++------------ .../e2e/spec/Payment/00022-Variations.cy.js | 8 +++ .../Payment/00024-ConnectorAgnosticNTID.cy.js | 29 ++++++-- 5 files changed, 84 insertions(+), 48 deletions(-) diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index 67efcdb0ea3..dbef78355de 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -13,7 +13,7 @@ concurrency: env: CARGO_INCREMENTAL: 0 CARGO_NET_RETRY: 10 - PAYMENTS_CONNECTORS: "cybersource" + PAYMENTS_CONNECTORS: "cybersource stripe" PAYOUTS_CONNECTORS: "wise" RUST_BACKTRACE: short RUSTUP_MAX_RETRIES: 10 diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index 5a9baa32d73..b3de4db78d4 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -4,7 +4,7 @@ import { connectorDetails as adyenConnectorDetails } from "./Adyen.js"; import { connectorDetails as bankOfAmericaConnectorDetails } from "./BankOfAmerica.js"; import { connectorDetails as bluesnapConnectorDetails } from "./Bluesnap.js"; import { connectorDetails as checkoutConnectorDetails } from "./Checkout.js"; -import { connectorDetails as CommonConnectorDetails } from "./Commons.js"; +import { connectorDetails as commonConnectorDetails } from "./Commons.js"; import { updateDefaultStatusCode } from "./Modifiers.js"; import { connectorDetails as cybersourceConnectorDetails } from "./Cybersource.js"; import { connectorDetails as datatransConnectorDetails } from "./Datatrans.js"; @@ -32,7 +32,7 @@ const connectorDetails = { bankofamerica: bankOfAmericaConnectorDetails, bluesnap: bluesnapConnectorDetails, checkout: checkoutConnectorDetails, - commons: CommonConnectorDetails, + commons: commonConnectorDetails, cybersource: cybersourceConnectorDetails, deutschebank: deutschebankConnectorDetails, fiservemea: fiservemeaConnectorDetails, @@ -307,3 +307,26 @@ export function updateBusinessProfile( profilePrefix ); } + +export const CONNECTOR_LISTS = { + // Exclusion lists (skip these connectors) + EXCLUDE: { + CONNECTOR_AGNOSTIC_NTID: ["paypal"], + // Add more exclusion lists + }, + + // Inclusion lists (only run for these connectors) + INCLUDE: { + MANDATES_USING_NTID_PROXY: ["cybersource"], + // Add more inclusion lists + }, +}; + +// Helper functions +export const shouldExcludeConnector = (connectorId, list) => { + return list.includes(connectorId); +}; + +export const shouldIncludeConnector = (connectorId, list) => { + return !list.includes(connectorId); +}; diff --git a/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js index 1554436abfb..67697cac6c1 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js @@ -6,11 +6,31 @@ let globalState; let connector; describe("Card - Mandates using Network Transaction Id flow test", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - connector = globalState.get("connectorId"); - }); + before(function () { + // Changed to regular function instead of arrow function + let skip = false; + + cy.task("getGlobalState") + .then((state) => { + globalState = new State(state); + connector = globalState.get("connectorId"); + + // Skip the test if the connector is not in the inclusion list + // This is done because only cybersource is known to support at present + if ( + utils.shouldIncludeConnector( + connector, + utils.CONNECTOR_LISTS.INCLUDE.MANDATES_USING_NTID_PROXY + ) + ) { + skip = true; + } + }) + .then(() => { + if (skip) { + this.skip(); + } + }); }); afterEach("flush global state", () => { @@ -20,12 +40,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { context( "Card - NoThreeDS Create and Confirm Automatic MIT payment flow test", () => { - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -46,12 +60,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { context( "Card - NoThreeDS Create and Confirm Manual MIT payment flow test", () => { - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -72,12 +80,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { context( "Card - NoThreeDS Create and Confirm Automatic multiple MITs payment flow test", () => { - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -114,12 +116,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { () => { let shouldContinue = true; - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT 1", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -177,12 +173,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { context( "Card - ThreeDS Create and Confirm Automatic multiple MITs payment flow test", () => { - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -217,12 +207,6 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { context( "Card - ThreeDS Create and Confirm Manual multiple MITs payment flow", () => { - beforeEach(function () { - if (connector !== "cybersource") { - this.skip(); - } - }); - it("Confirm No 3DS MIT", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" diff --git a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js index f1499dc93bd..5a1f765cf62 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js @@ -222,6 +222,14 @@ describe("Corner cases", () => { if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + it("Capture call", () => { const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ "CaptureGreaterAmount" diff --git a/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js index 4db0ad99900..308a8c036ca 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js @@ -4,6 +4,7 @@ import { payment_methods_enabled } from "../../configs/Payment/Commons"; import getConnectorDetails, * as utils from "../../configs/Payment/Utils"; let globalState; +let connector; /* Flow: @@ -32,10 +33,30 @@ Flow: */ describe("Connector Agnostic Tests", () => { - before("seed global state", () => { - cy.task("getGlobalState").then((state) => { - globalState = new State(state); - }); + before(function () { + // Changed to regular function instead of arrow function + let skip = false; + + cy.task("getGlobalState") + .then((state) => { + globalState = new State(state); + connector = globalState.get("connectorId"); + + // Skip running test against a connector that is added in the exclude list + if ( + utils.shouldExcludeConnector( + connector, + utils.CONNECTOR_LISTS.EXCLUDE.CONNECTOR_AGNOSTIC_NTID + ) + ) { + skip = true; + } + }) + .then(() => { + if (skip) { + this.skip(); + } + }); }); after("flush global state", () => { From c044ffff0c47ee5d3ef5f905c3f590fae4ac9a24 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 6 Feb 2025 15:05:29 +0530 Subject: [PATCH 050/133] chore(connector): [Fiuu] log keys in the PSync response (#7189) --- .../src/connectors/fiuu.rs | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 70bca41582c..4dc87c244ac 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -1,6 +1,10 @@ pub mod transformers; -use std::collections::{HashMap, HashSet}; +use std::{ + any::type_name, + borrow::Cow, + collections::{HashMap, HashSet}, +}; use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; use common_utils::{ @@ -53,6 +57,40 @@ use crate::{ utils::{self, PaymentMethodDataType}, }; +pub fn parse_and_log_keys_in_url_encoded_response(data: &[u8]) { + match std::str::from_utf8(data) { + Ok(query_str) => { + let loggable_keys = [ + "status", + "orderid", + "tranID", + "nbcb", + "amount", + "currency", + "paydate", + "channel", + "error_desc", + "error_code", + "extraP", + ]; + let keys: Vec<(Cow<'_, str>, String)> = + url::form_urlencoded::parse(query_str.as_bytes()) + .map(|(key, value)| { + if loggable_keys.contains(&key.to_string().as_str()) { + (key, value.to_string()) + } else { + (key, "SECRET".to_string()) + } + }) + .collect(); + router_env::logger::info!("Keys in {} response\n{:?}", type_name::(), keys); + } + Err(err) => { + router_env::logger::error!("Failed to convert bytes to string: {:?}", err); + } + } +} + fn parse_response(data: &[u8]) -> Result where T: for<'de> Deserialize<'de>, @@ -87,6 +125,27 @@ where json.insert("miscellaneous".to_string(), misc_value); } + // TODO: Remove this after debugging + let loggable_keys = [ + "StatCode", + "StatName", + "TranID", + "ErrorCode", + "ErrorDesc", + "miscellaneous", + ]; + let keys: Vec<(&str, Value)> = json + .iter() + .map(|(key, value)| { + if loggable_keys.contains(&key.as_str()) { + (key.as_str(), value.to_owned()) + } else { + (key.as_str(), Value::String("SECRET".to_string())) + } + }) + .collect(); + router_env::logger::info!("Keys in response for type {}\n{:?}", type_name::(), keys); + let response: T = serde_json::from_value(Value::Object(json)).map_err(|e| { router_env::logger::error!("Error in Deserializing Response Data: {:?}", e); errors::ConnectorError::ResponseDeserializationFailed @@ -747,6 +806,7 @@ impl webhooks::IncomingWebhook for Fiuu { ) -> CustomResult, errors::ConnectorError> { let header = utils::get_header_key_value("content-type", request.headers)?; let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + parse_and_log_keys_in_url_encoded_response::(request.body); serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? } else { @@ -776,6 +836,7 @@ impl webhooks::IncomingWebhook for Fiuu { ) -> CustomResult, errors::ConnectorError> { let header = utils::get_header_key_value("content-type", request.headers)?; let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + parse_and_log_keys_in_url_encoded_response::(request.body); serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookSourceVerificationFailed)? } else { @@ -833,6 +894,7 @@ impl webhooks::IncomingWebhook for Fiuu { ) -> CustomResult { let header = utils::get_header_key_value("content-type", request.headers)?; let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + parse_and_log_keys_in_url_encoded_response::(request.body); serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)? } else { @@ -866,6 +928,7 @@ impl webhooks::IncomingWebhook for Fiuu { ) -> CustomResult { let header = utils::get_header_key_value("content-type", request.headers)?; let resource: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + parse_and_log_keys_in_url_encoded_response::(request.body); serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookEventTypeNotFound)? } else { @@ -891,6 +954,7 @@ impl webhooks::IncomingWebhook for Fiuu { ) -> CustomResult, errors::ConnectorError> { let header = utils::get_header_key_value("content-type", request.headers)?; let payload: FiuuWebhooksResponse = if header == "application/x-www-form-urlencoded" { + parse_and_log_keys_in_url_encoded_response::(request.body); serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)? } else { @@ -921,6 +985,9 @@ impl webhooks::IncomingWebhook for Fiuu { Option, errors::ConnectorError, > { + parse_and_log_keys_in_url_encoded_response::( + request.body, + ); let webhook_payment_response: transformers::FiuuWebhooksPaymentResponse = serde_urlencoded::from_bytes::(request.body) .change_context(errors::ConnectorError::WebhookResourceObjectNotFound)?; From f9a4713a60028e26b98143c6296d9969cd090163 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Thu, 6 Feb 2025 15:57:34 +0530 Subject: [PATCH 051/133] refactor(router): store `network_transaction_id` for `off_session` payments irrespective of the `is_connector_agnostic_mit_enabled` config (#7083) Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Co-authored-by: Pa1NarK <69745008+pixincreate@users.noreply.github.com> --- .../payments/operations/payment_response.rs | 30 +- .../router/src/core/payments/tokenization.rs | 11 +- .../Payment/00024-ConnectorAgnosticNTID.cy.js | 369 +++++++++++++++++- 3 files changed, 385 insertions(+), 25 deletions(-) diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 748b71469ba..70d5f79845b 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -576,7 +576,7 @@ impl PostUpdateTracker, types::PaymentsSyncData> for merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, - business_profile: &domain::Profile, + _business_profile: &domain::Profile, ) -> CustomResult<(), errors::ApiErrorResponse> where F: 'b + Clone + Send + Sync, @@ -617,7 +617,6 @@ impl PostUpdateTracker, types::PaymentsSyncData> for resp.status, resp.response.clone(), merchant_account.storage_scheme, - business_profile.is_connector_agnostic_mit_enabled, ) .await?; Ok(()) @@ -1201,7 +1200,7 @@ impl PostUpdateTracker, types::CompleteAuthorizeData merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, payment_data: &mut PaymentData, - business_profile: &domain::Profile, + _business_profile: &domain::Profile, ) -> CustomResult<(), errors::ApiErrorResponse> where F: 'b + Clone + Send + Sync, @@ -1241,7 +1240,6 @@ impl PostUpdateTracker, types::CompleteAuthorizeData resp.status, resp.response.clone(), merchant_account.storage_scheme, - business_profile.is_connector_agnostic_mit_enabled, ) .await?; Ok(()) @@ -2068,7 +2066,6 @@ async fn update_payment_method_status_and_ntid( attempt_status: common_enums::AttemptStatus, payment_response: Result, storage_scheme: enums::MerchantStorageScheme, - is_connector_agnostic_mit_enabled: Option, ) -> RouterResult<()> { todo!() } @@ -2084,7 +2081,6 @@ async fn update_payment_method_status_and_ntid( attempt_status: common_enums::AttemptStatus, payment_response: Result, storage_scheme: enums::MerchantStorageScheme, - is_connector_agnostic_mit_enabled: Option, ) -> RouterResult<()> { // If the payment_method is deleted then ignore the error related to retrieving payment method // This should be handled when the payment method is soft deleted @@ -2119,20 +2115,18 @@ async fn update_payment_method_status_and_ntid( }) .ok() .flatten(); - let network_transaction_id = - if let Some(network_transaction_id) = pm_resp_network_transaction_id { - if is_connector_agnostic_mit_enabled == Some(true) - && payment_data.payment_intent.setup_future_usage - == Some(diesel_models::enums::FutureUsage::OffSession) - { - Some(network_transaction_id) - } else { - logger::info!("Skip storing network transaction id"); - None - } + let network_transaction_id = if payment_data.payment_intent.setup_future_usage + == Some(diesel_models::enums::FutureUsage::OffSession) + { + if pm_resp_network_transaction_id.is_some() { + pm_resp_network_transaction_id } else { + logger::info!("Skip storing network transaction id"); None - }; + } + } else { + None + }; let pm_update = if payment_method.status != common_enums::PaymentMethodStatus::Active && payment_method.status != attempt_status.into() diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index 597831231a3..d46977d8c99 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -111,12 +111,11 @@ where }; let network_transaction_id = - if let Some(network_transaction_id) = network_transaction_id { - if business_profile.is_connector_agnostic_mit_enabled == Some(true) - && save_payment_method_data.request.get_setup_future_usage() - == Some(storage_enums::FutureUsage::OffSession) - { - Some(network_transaction_id) + if save_payment_method_data.request.get_setup_future_usage() + == Some(storage_enums::FutureUsage::OffSession) + { + if network_transaction_id.is_some() { + network_transaction_id } else { logger::info!("Skip storing network transaction id"); None diff --git a/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js index 308a8c036ca..8491cb0b8c0 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00024-ConnectorAgnosticNTID.cy.js @@ -13,13 +13,38 @@ Flow: - Make a Payment - List Payment Method for Customer using Client Secret (will get PMID) +- Create Business Profile with connector agnostic feature disabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer -- Empty list; i.e., no payment method should be listed +- Confirm Payment with PMID from previous step (should fail as Connector Mandate ID is not present in the newly created Profile) + + - Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature disabled - Create Merchant Connector Account - Create Payment Intent - List Payment Method for Customer -- Empty list; i.e., no payment method should be listed - Confirm Payment with PMID from previous step (should fail as Connector Mandate ID is not present in the newly created Profile) +- Create Business Profile with connector agnostic feature disabled +- Create Merchant Connector Account and Customer +- Make a Payment +- List Payment Method for Customer using Client Secret (will get PMID) + +- Create Business Profile with connector agnostic feature enabled +- Create Merchant Connector Account +- Create Payment Intent +- List Payment Method for Customer using Client Secret (will get PMID which is same as the one from previous step along with Payment Token) +- Confirm Payment with PMID from previous step (should pass as NTID is present in the DB) + + + - Create Business Profile with connector agnostic feature enabled - Create Merchant Connector Account and Customer - Make a Payment @@ -62,8 +87,9 @@ describe("Connector Agnostic Tests", () => { after("flush global state", () => { cy.task("setGlobalState", globalState.data); }); + context( - "Connector Agnostic Disabled for Profile 1 and Enabled for Profile 2", + "Connector Agnostic Disabled for both Profile 1 and Profile 2", () => { let shouldContinue = true; @@ -141,6 +167,121 @@ describe("Connector Agnostic Tests", () => { ); }); + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["MITAutoCapture"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + newData, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + const commonData = getConnectorDetails(globalState.get("commons"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + const newData = { + ...data, + Response: utils.getConnectorFlowDetails( + data, + commonData, + "ResponseCustom" + ), + }; + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + newData, + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + + context( + "Connector Agnostic Enabled for Profile 1 and Disabled for Profile 2", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + it("Enable Connector Agnostic for Business Profile", () => { utils.updateBusinessProfile( fixtures.businessProfile.bpUpdate, @@ -153,6 +294,67 @@ describe("Connector Agnostic Tests", () => { ); }); + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer using Client Secret", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + it("Create Payment Intent", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" @@ -250,6 +452,171 @@ describe("Connector Agnostic Tests", () => { } ); + context( + "Connector Agnostic Disabled for Profile 1 and Enabled for Profile 2", + () => { + let shouldContinue = true; + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardUseNo3DSAutoCaptureOffSession"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer using Client Secret", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Create business profile", () => { + utils.createBusinessProfile( + fixtures.businessProfile.bpCreate, + globalState + ); + }); + + it("Create merchant connector account", () => { + utils.createMerchantConnectorAccount( + "payment_processor", + fixtures.createConnectorBody, + globalState, + payment_methods_enabled + ); + }); + + it("Enable Connector Agnostic for Business Profile", () => { + utils.updateBusinessProfile( + fixtures.businessProfile.bpUpdate, + true, // is_connector_agnostic_enabled + false, // collect_billing_address_from_wallet_connector + false, // collect_shipping_address_from_wallet_connector + false, // always_collect_billing_address_from_wallet_connector + false, // always_collect_shipping_address_from_wallet_connector + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (PMID)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MITAutoCapture"]; + + cy.mitUsingPMId( + fixtures.pmIdConfirmBody, + data, + 7000, + true, + "automatic", + globalState + ); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentOffSession"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("List Payment Method for Customer", () => { + cy.listCustomerPMByClientSecret(globalState); + }); + + it("Confirm No 3DS MIT (Token)", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SaveCardConfirmAutoCaptureOffSession"]; + + cy.saveCardConfirmCallTest( + fixtures.saveCardConfirmBody, + data, + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + } + ); + context("Connector Agnostic Enabled for Profile 1 and Profile 2", () => { let shouldContinue = true; From 775dcc5a4e3b41dd1e4d0e4c47eccca15a8a4b3a Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 6 Feb 2025 19:13:13 +0530 Subject: [PATCH 052/133] chore(roles): remove redundant variant from PermissionGroup (#6985) --- crates/common_enums/src/enums.rs | 2 -- crates/router/src/services/authorization/info.rs | 6 +++--- .../router/src/services/authorization/permission_groups.rs | 5 ++--- .../down.sql | 1 + .../up.sql | 3 +++ 5 files changed, 9 insertions(+), 8 deletions(-) create mode 100644 migrations/2025-01-03-104019_migrate_permission_group_for_recon/down.sql create mode 100644 migrations/2025-01-03-104019_migrate_permission_group_for_recon/up.sql diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 0234fbea6d5..1faa3b90aa8 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2936,8 +2936,6 @@ pub enum PermissionGroup { ReconReportsManage, ReconOpsView, ReconOpsManage, - // TODO: To be deprecated, make sure DB is migrated before removing - ReconOps, } #[derive(Clone, Debug, serde::Serialize, PartialEq, Eq, Hash, strum::EnumIter)] diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index e02838b3e00..b4413dfa3b3 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -40,10 +40,10 @@ fn get_group_description(group: PermissionGroup) -> &'static str { PermissionGroup::MerchantDetailsView | PermissionGroup::AccountView => "View Merchant Details", PermissionGroup::MerchantDetailsManage | PermissionGroup::AccountManage => "Create, modify and delete Merchant Details like api keys, webhooks, etc", PermissionGroup::OrganizationManage => "Manage organization level tasks like create new Merchant accounts, Organization level roles, etc", - PermissionGroup::ReconReportsView => "View and access reconciliation reports and analytics", + PermissionGroup::ReconReportsView => "View reconciliation reports and analytics", PermissionGroup::ReconReportsManage => "Manage reconciliation reports", - PermissionGroup::ReconOpsView => "View and access reconciliation operations", - PermissionGroup::ReconOpsManage | PermissionGroup::ReconOps => "Manage reconciliation operations", + PermissionGroup::ReconOpsView => "View and access all reconciliation operations including reports and analytics", + PermissionGroup::ReconOpsManage => "Manage all reconciliation operations including reports and analytics", } } diff --git a/crates/router/src/services/authorization/permission_groups.rs b/crates/router/src/services/authorization/permission_groups.rs index ceb943950d5..0cdb68ec8d7 100644 --- a/crates/router/src/services/authorization/permission_groups.rs +++ b/crates/router/src/services/authorization/permission_groups.rs @@ -33,7 +33,6 @@ impl PermissionGroupExt for PermissionGroup { | Self::OrganizationManage | Self::AccountManage | Self::ReconOpsManage - | Self::ReconOps | Self::ReconReportsManage => PermissionScope::Write, } } @@ -50,7 +49,7 @@ impl PermissionGroupExt for PermissionGroup { | Self::MerchantDetailsManage | Self::AccountView | Self::AccountManage => ParentGroup::Account, - Self::ReconOpsView | Self::ReconOpsManage | Self::ReconOps => ParentGroup::ReconOps, + Self::ReconOpsView | Self::ReconOpsManage => ParentGroup::ReconOps, Self::ReconReportsView | Self::ReconReportsManage => ParentGroup::ReconReports, } } @@ -86,7 +85,7 @@ impl PermissionGroupExt for PermissionGroup { } Self::ReconOpsView => vec![Self::ReconOpsView], - Self::ReconOpsManage | Self::ReconOps => vec![Self::ReconOpsView, Self::ReconOpsManage], + Self::ReconOpsManage => vec![Self::ReconOpsView, Self::ReconOpsManage], Self::ReconReportsView => vec![Self::ReconReportsView], Self::ReconReportsManage => vec![Self::ReconReportsView, Self::ReconReportsManage], diff --git a/migrations/2025-01-03-104019_migrate_permission_group_for_recon/down.sql b/migrations/2025-01-03-104019_migrate_permission_group_for_recon/down.sql new file mode 100644 index 00000000000..e0ac49d1ecf --- /dev/null +++ b/migrations/2025-01-03-104019_migrate_permission_group_for_recon/down.sql @@ -0,0 +1 @@ +SELECT 1; diff --git a/migrations/2025-01-03-104019_migrate_permission_group_for_recon/up.sql b/migrations/2025-01-03-104019_migrate_permission_group_for_recon/up.sql new file mode 100644 index 00000000000..0fa04632dce --- /dev/null +++ b/migrations/2025-01-03-104019_migrate_permission_group_for_recon/up.sql @@ -0,0 +1,3 @@ +UPDATE roles +SET groups = array_replace(groups, 'recon_ops', 'recon_ops_manage') +WHERE 'recon_ops' = ANY(groups); From dddb1b06bea4ac89d838641508728d2da4326ba1 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:13:36 +0530 Subject: [PATCH 053/133] feat(connector): [COINGATE] Add Template PR (#7052) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 2 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/common_enums/src/connector_enums.rs | 1 + .../hyperswitch_connectors/src/connectors.rs | 21 +- .../src/connectors/coingate.rs | 571 ++++++++++++++++++ .../src/connectors/coingate/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 35 ++ .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 32 +- crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 1 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/coingate.rs | 420 +++++++++++++ crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 2 + scripts/add_connector.sh | 2 +- 21 files changed, 1323 insertions(+), 27 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/coingate.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs create mode 100644 crates/router/tests/connectors/coingate.rs diff --git a/config/config.example.toml b/config/config.example.toml index e982a01eb11..7bb6d8fc567 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -196,6 +196,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" @@ -310,6 +311,7 @@ cards = [ "adyenplatform", "authorizedotnet", "coinbase", + "coingate", "cryptopay", "braintree", "checkout", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 5791c6aa83e..4d0bce877e8 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -42,6 +42,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 78590f05f1b..8964fbc2e20 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -46,6 +46,7 @@ cashtocode.base_url = "https://cluster14.api.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api.coingate.com/v2" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 4127a694fcb..e98bd1e2fad 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -46,6 +46,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" checkout.base_url = "https://api.sandbox.checkout.com/" chargebee.base_url = "https://$.chargebee.com/api/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/development.toml b/config/development.toml index 473c453d1c8..f61321fda0f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -108,6 +108,7 @@ cards = [ "braintree", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", @@ -215,6 +216,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 12b8df6683c..ed8f99d20df 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -128,6 +128,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" @@ -233,6 +234,7 @@ cards = [ "braintree", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 28dbf33d97d..a0e606e0741 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -68,6 +68,7 @@ pub enum RoutableConnectors { // Chargebee, Checkout, Coinbase, + // Coingate, Cryptopay, Cybersource, Datatrans, diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 15345c8221f..e9738a4907a 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -10,6 +10,7 @@ pub mod boku; pub mod cashtocode; pub mod chargebee; pub mod coinbase; +pub mod coingate; pub mod cryptopay; pub mod ctp_mastercard; pub mod cybersource; @@ -61,16 +62,16 @@ pub use self::{ airwallex::Airwallex, amazonpay::Amazonpay, bambora::Bambora, bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, boku::Boku, cashtocode::Cashtocode, chargebee::Chargebee, coinbase::Coinbase, - cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, cybersource::Cybersource, - datatrans::Datatrans, deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, - elavon::Elavon, fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, - globepay::Globepay, gocardless::Gocardless, helcim::Helcim, inespay::Inespay, - jpmorgan::Jpmorgan, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, - nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, - payu::Payu, placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, - rapyd::Rapyd, razorpay::Razorpay, redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, - taxjar::Taxjar, thunes::Thunes, tsys::Tsys, - unified_authentication_service::UnifiedAuthenticationService, volt::Volt, + coingate::Coingate, cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, + cybersource::Cybersource, datatrans::Datatrans, deutschebank::Deutschebank, + digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, fiserv::Fiserv, + fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globepay::Globepay, gocardless::Gocardless, + helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, + multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, + novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, payu::Payu, placetopay::Placetopay, + powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, + redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, + tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, volt::Volt, wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/coingate.rs b/crates/hyperswitch_connectors/src/connectors/coingate.rs new file mode 100644 index 00000000000..e3bece084b3 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/coingate.rs @@ -0,0 +1,571 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as coingate; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Coingate { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Coingate { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Coingate {} +impl api::PaymentSession for Coingate {} +impl api::ConnectorAccessToken for Coingate {} +impl api::MandateSetup for Coingate {} +impl api::PaymentAuthorize for Coingate {} +impl api::PaymentSync for Coingate {} +impl api::PaymentCapture for Coingate {} +impl api::PaymentVoid for Coingate {} +impl api::Refund for Coingate {} +impl api::RefundExecute for Coingate {} +impl api::RefundSync for Coingate {} +impl api::PaymentToken for Coingate {} + +impl ConnectorIntegration + for Coingate +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Coingate +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Coingate { + fn id(&self) -> &'static str { + "coingate" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.coingate.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = coingate::CoingateAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: coingate::CoingateErrorResponse = res + .response + .parse_struct("CoingateErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Coingate { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Coingate { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Coingate {} + +impl ConnectorIntegration + for Coingate +{ +} + +impl ConnectorIntegration for Coingate { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = coingate::CoingateRouterData::from((amount, req)); + let connector_req = coingate::CoingatePaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: coingate::CoingatePaymentsResponse = res + .response + .parse_struct("Coingate PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Coingate { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: coingate::CoingatePaymentsResponse = res + .response + .parse_struct("coingate PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Coingate { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: coingate::CoingatePaymentsResponse = res + .response + .parse_struct("Coingate PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Coingate {} + +impl ConnectorIntegration for Coingate { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = coingate::CoingateRouterData::from((refund_amount, req)); + let connector_req = coingate::CoingateRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: coingate::RefundResponse = res + .response + .parse_struct("coingate RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Coingate { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: coingate::RefundResponse = res + .response + .parse_struct("coingate RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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] +impl webhooks::IncomingWebhook for Coingate { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Coingate {} diff --git a/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs new file mode 100644 index 00000000000..08b9ae781e1 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct CoingateRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for CoingateRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct CoingatePaymentsRequest { + amount: StringMinorUnit, + card: CoingateCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct CoingateCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&CoingateRouterData<&PaymentsAuthorizeRouterData>> for CoingatePaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &CoingateRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = CoingateCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct CoingateAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for CoingateAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum CoingatePaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: CoingatePaymentStatus) -> Self { + match item { + CoingatePaymentStatus::Succeeded => Self::Charged, + CoingatePaymentStatus::Failed => Self::Failure, + CoingatePaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct CoingatePaymentsResponse { + status: CoingatePaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct CoingateRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&CoingateRouterData<&RefundsRouterData>> for CoingateRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &CoingateRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct CoingateErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index f58a4802d50..cfc65de64e8 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -107,6 +107,7 @@ default_imp_for_authorize_session_token!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -181,6 +182,7 @@ default_imp_for_calculate_tax!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -255,6 +257,7 @@ default_imp_for_session_update!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -330,6 +333,7 @@ default_imp_for_post_session_tokens!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -404,6 +408,7 @@ default_imp_for_complete_authorize!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Datatrans, connectors::Dlocal, @@ -470,6 +475,7 @@ default_imp_for_incremental_authorization!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Datatrans, connectors::Deutschebank, @@ -544,6 +550,7 @@ default_imp_for_create_customer!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -617,6 +624,7 @@ default_imp_for_connector_redirect_response!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -685,6 +693,7 @@ default_imp_for_pre_processing_steps!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Datatrans, connectors::Deutschebank, @@ -757,6 +766,7 @@ default_imp_for_post_processing_steps!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -833,6 +843,7 @@ default_imp_for_approve!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -909,6 +920,7 @@ default_imp_for_reject!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -985,6 +997,7 @@ default_imp_for_webhook_source_verification!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1062,6 +1075,7 @@ default_imp_for_accept_dispute!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1138,6 +1152,7 @@ default_imp_for_submit_evidence!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1214,6 +1229,7 @@ default_imp_for_defend_dispute!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1299,6 +1315,7 @@ default_imp_for_file_upload!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1370,6 +1387,7 @@ default_imp_for_payouts!( connectors::Cryptopay, connectors::Datatrans, connectors::Coinbase, + connectors::Coingate, connectors::Deutschebank, connectors::Digitalvirgo, connectors::Dlocal, @@ -1444,6 +1462,7 @@ default_imp_for_payouts_create!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1522,6 +1541,7 @@ default_imp_for_payouts_retrieve!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1600,6 +1620,7 @@ default_imp_for_payouts_eligibility!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1678,6 +1699,7 @@ default_imp_for_payouts_fulfill!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Datatrans, connectors::Deutschebank, @@ -1755,6 +1777,7 @@ default_imp_for_payouts_cancel!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1833,6 +1856,7 @@ default_imp_for_payouts_quote!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1911,6 +1935,7 @@ default_imp_for_payouts_recipient!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -1989,6 +2014,7 @@ default_imp_for_payouts_recipient_account!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2067,6 +2093,7 @@ default_imp_for_frm_sale!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2145,6 +2172,7 @@ default_imp_for_frm_checkout!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2223,6 +2251,7 @@ default_imp_for_frm_transaction!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2301,6 +2330,7 @@ default_imp_for_frm_fulfillment!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2379,6 +2409,7 @@ default_imp_for_frm_record_return!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Cybersource, connectors::Datatrans, @@ -2454,6 +2485,7 @@ default_imp_for_revoking_mandates!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::Datatrans, connectors::Deutschebank, @@ -2528,6 +2560,7 @@ default_imp_for_uas_pre_authentication!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -2602,6 +2635,7 @@ default_imp_for_uas_post_authentication!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -2676,6 +2710,7 @@ default_imp_for_uas_authentication!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 9f0b319bfa1..51607778eca 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -217,6 +217,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -294,6 +295,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -366,6 +368,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -444,6 +447,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Datatrans, @@ -520,6 +524,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Datatrans, @@ -596,6 +601,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -683,6 +689,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -762,6 +769,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -841,6 +849,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -920,6 +929,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -999,6 +1009,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1078,6 +1089,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1157,6 +1169,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1236,6 +1249,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1315,6 +1329,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1392,6 +1407,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1471,6 +1487,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1550,6 +1567,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1629,6 +1647,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1708,6 +1727,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1787,6 +1807,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, @@ -1863,6 +1884,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, + connectors::Coingate, connectors::Cryptopay, connectors::CtpMastercard, connectors::Cybersource, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index efd41aaa4ac..c832507f92e 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -27,6 +27,7 @@ pub struct Connectors { pub chargebee: ConnectorParams, pub checkout: ConnectorParams, pub coinbase: ConnectorParams, + pub coingate: ConnectorParams, pub cryptopay: ConnectorParams, pub ctp_mastercard: NoParams, pub cybersource: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index c926401107c..a8cdf2b6cf8 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -37,22 +37,22 @@ pub use hyperswitch_connectors::connectors::{ bamboraapac, bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, bluesnap::Bluesnap, boku, boku::Boku, cashtocode, cashtocode::Cashtocode, chargebee::Chargebee, coinbase, coinbase::Coinbase, - cryptopay, cryptopay::Cryptopay, ctp_mastercard, ctp_mastercard::CtpMastercard, cybersource, - cybersource::Cybersource, datatrans, datatrans::Datatrans, deutschebank, - deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, - elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, - fiuu::Fiuu, forte, forte::Forte, globepay, globepay::Globepay, gocardless, - gocardless::Gocardless, helcim, helcim::Helcim, inespay, inespay::Inespay, jpmorgan, - jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, nexinets, - nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, nomupay, nomupay::Nomupay, novalnet, - novalnet::Novalnet, paybox, paybox::Paybox, payeezy, payeezy::Payeezy, payu, payu::Payu, - placetopay, placetopay::Placetopay, powertranz, powertranz::Powertranz, prophetpay, - prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, redsys, - redsys::Redsys, shift4, shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, - taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, unified_authentication_service, - unified_authentication_service::UnifiedAuthenticationService, volt, volt::Volt, wellsfargo, - wellsfargo::Wellsfargo, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, xendit, - xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, + coingate, coingate::Coingate, cryptopay, cryptopay::Cryptopay, ctp_mastercard, + ctp_mastercard::CtpMastercard, cybersource, cybersource::Cybersource, datatrans, + datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, + digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, elavon, elavon::Elavon, fiserv, + fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, fiuu::Fiuu, forte, forte::Forte, + globepay, globepay::Globepay, gocardless, gocardless::Gocardless, helcim, helcim::Helcim, + inespay, inespay::Inespay, jpmorgan, jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, + multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, + nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, paybox, paybox::Paybox, payeezy, + payeezy::Payeezy, payu, payu::Payu, placetopay, placetopay::Placetopay, powertranz, + powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, + razorpay::Razorpay, redsys, redsys::Redsys, shift4, shift4::Shift4, square, square::Square, + stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, + unified_authentication_service, unified_authentication_service::UnifiedAuthenticationService, + volt, volt::Volt, wellsfargo, wellsfargo::Wellsfargo, worldline, worldline::Worldline, + worldpay, worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, }; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 0c80c5a70e2..852b5d95209 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -435,6 +435,7 @@ default_imp_for_connector_request_id!( connector::Chargebee, connector::Checkout, connector::Coinbase, + connector::Coingate, connector::Cryptopay, connector::Cybersource, connector::Datatrans, @@ -1528,6 +1529,7 @@ default_imp_for_fraud_check!( connector::Cryptopay, connector::Cybersource, connector::Coinbase, + connector::Coingate, connector::Datatrans, connector::Deutschebank, connector::Digitalvirgo, @@ -2117,6 +2119,7 @@ default_imp_for_connector_authentication!( connector::Checkout, connector::Cryptopay, connector::Coinbase, + connector::Coingate, connector::Cybersource, connector::Datatrans, connector::Deutschebank, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 4dc5da6475f..addb34eb973 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -368,6 +368,7 @@ impl ConnectorData { enums::Connector::Coinbase => { Ok(ConnectorEnum::Old(Box::new(&connector::Coinbase))) } + // enums::Connector::Coingate => Ok(ConnectorEnum::Old(Box::new(connector::Coingate))), enums::Connector::Cryptopay => { Ok(ConnectorEnum::Old(Box::new(connector::Cryptopay::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index c8222163b52..3e81d939c14 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -228,6 +228,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::Chargebee => Self::Chargebee, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, + // api_enums::Connector::Coingate => Self::Coingate, api_enums::Connector::Cryptopay => Self::Cryptopay, api_enums::Connector::CtpMastercard => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/crates/router/tests/connectors/coingate.rs b/crates/router/tests/connectors/coingate.rs new file mode 100644 index 00000000000..572aecb3dd7 --- /dev/null +++ b/crates/router/tests/connectors/coingate.rs @@ -0,0 +1,420 @@ +use masking::Secret; +use router::types::{self, api, storage::enums}; + +use crate::utils::{self, ConnectorActions}; +use test_utils::connector_auth; + +#[derive(Clone, Copy)] +struct CoingateTest; +impl ConnectorActions for CoingateTest {} +impl utils::Connector for CoingateTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Coingate; + api::ConnectorData { + connector: Box::new(Coingate::new()), + connector_name: types::Connector::Coingate, + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .coingate + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "coingate".to_string() + } +} + +static CONNECTOR: CoingateTest = CoingateTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::api::PaymentMethodData::Card(api::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: api::PaymentMethodData::Card(api::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 3b02661c5ce..0a3aa4bcff0 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -30,6 +30,7 @@ pub struct ConnectorAuthentication { pub chargebee: Option, pub checkout: Option, pub coinbase: Option, + pub coingate: Option, pub cryptopay: Option, pub cybersource: Option, pub datatrans: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 4d9c686f23b..2fecba27bab 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -94,6 +94,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" +coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" @@ -199,6 +200,7 @@ cards = [ "braintree", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 32d24fe47eb..8dee6cd21f9 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase coingate cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From a6367d92f629ef01cdb73aded8a81d2ba198f38c Mon Sep 17 00:00:00 2001 From: sweta-kumari-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:15:48 +0530 Subject: [PATCH 054/133] refactor(dynamic_fields): dynamic fields for Adyen and Stripe, renaming klarnaCheckout, WASM for KlarnaCheckout (#7015) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 11 ----------- api-reference/openapi_spec.json | 11 ----------- crates/api_models/src/payments.rs | 3 --- crates/connector_configs/src/transformer.rs | 3 ++- crates/connector_configs/toml/development.toml | 4 ++++ crates/connector_configs/toml/production.toml | 4 ++++ crates/connector_configs/toml/sandbox.toml | 4 ++++ .../src/connectors/multisafepay/transformers.rs | 1 - .../src/connectors/square/transformers.rs | 1 - .../src/connectors/zen/transformers.rs | 1 - crates/hyperswitch_connectors/src/utils.rs | 2 -- .../src/payment_method_data.rs | 3 --- .../defaults/payment_connector_required_fields.rs | 8 ++++---- crates/router/src/connector/adyen/transformers.rs | 3 +-- crates/router/src/connector/klarna.rs | 2 +- crates/router/src/connector/klarna/transformers.rs | 2 +- crates/router/src/connector/nuvei/transformers.rs | 1 - crates/router/src/connector/paypal/transformers.rs | 1 - crates/router/src/connector/stripe/transformers.rs | 1 - crates/router/src/connector/utils.rs | 2 -- 20 files changed, 21 insertions(+), 47 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index a8abc3fb7d0..ebfbfc3bf06 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -12481,17 +12481,6 @@ } } }, - { - "type": "object", - "required": [ - "klarna_checkout" - ], - "properties": { - "klarna_checkout": { - "type": "object" - } - } - }, { "type": "object", "required": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 5bdaa5940c8..6c03d7a9a25 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -15599,17 +15599,6 @@ } } }, - { - "type": "object", - "required": [ - "klarna_checkout" - ], - "properties": { - "klarna_checkout": { - "type": "object" - } - } - }, { "type": "object", "required": [ diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index ec281197eda..45ba71454f4 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1876,7 +1876,6 @@ pub enum PayLaterData { /// The token for the sdk workflow token: String, }, - KlarnaCheckout {}, /// For Affirm redirect as PayLater Option AffirmRedirect {}, /// For AfterpayClearpay redirect as PayLater Option @@ -1934,7 +1933,6 @@ impl GetAddressFromPaymentMethodData for PayLaterData { | Self::WalleyRedirect {} | Self::AlmaRedirect {} | Self::KlarnaSdk { .. } - | Self::KlarnaCheckout {} | Self::AffirmRedirect {} | Self::AtomeRedirect {} => None, } @@ -2397,7 +2395,6 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, - Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, diff --git a/crates/connector_configs/src/transformer.rs b/crates/connector_configs/src/transformer.rs index 160e938ead3..ed3319816d5 100644 --- a/crates/connector_configs/src/transformer.rs +++ b/crates/connector_configs/src/transformer.rs @@ -45,10 +45,11 @@ impl DashboardRequestPayload { Some(api_models::enums::PaymentExperience::RedirectToUrl) } (Connector::Paypal, Paypal) => payment_experience, + (Connector::Klarna, Klarna) => payment_experience, (Connector::Zen, GooglePay) | (Connector::Zen, ApplePay) => { Some(api_models::enums::PaymentExperience::RedirectToUrl) } - (Connector::Braintree, Paypal) | (Connector::Klarna, Klarna) => { + (Connector::Braintree, Paypal) => { Some(api_models::enums::PaymentExperience::InvokeSdkClient) } (Connector::Globepay, AliPay) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 2b04ea8d1c2..45ae3ceea61 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1779,6 +1779,10 @@ key1="Client Secret" [klarna] [[klarna.pay_later]] payment_method_type = "klarna" + payment_experience = "invoke_sdk_client" +[[klarna.pay_later]] + payment_method_type = "klarna" + payment_experience = "redirect_to_url" [klarna.connector_auth.BodyKey] key1="Klarna Merchant Username" api_key="Klarna Merchant ID Password" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index b7108c63477..104121de0e1 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1491,6 +1491,10 @@ key1="Client Secret" [klarna] [[klarna.pay_later]] payment_method_type = "klarna" + payment_experience = "invoke_sdk_client" +[[klarna.pay_later]] + payment_method_type = "klarna" + payment_experience = "redirect_to_url" [klarna.connector_auth.BodyKey] key1="Klarna Merchant Username" api_key="Klarna Merchant ID Password" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index b886e7410a9..decfcf90b95 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1727,6 +1727,10 @@ key1="Client Secret" [klarna] [[klarna.pay_later]] payment_method_type = "klarna" + payment_experience = "invoke_sdk_client" +[[klarna.pay_later]] + payment_method_type = "klarna" + payment_experience = "redirect_to_url" [klarna.connector_auth.BodyKey] key1="Klarna Merchant Username" api_key="Klarna Merchant ID Password" diff --git a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs index 54aca37a76a..2065fdc232a 100644 --- a/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/multisafepay/transformers.rs @@ -749,7 +749,6 @@ impl TryFrom<&MultisafepayRouterData<&types::PaymentsAuthorizeRouterData>> email: Some(match paylater { PayLaterData::KlarnaRedirect {} => item.router_data.get_billing_email()?, PayLaterData::KlarnaSdk { token: _ } - | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect {} | PayLaterData::AfterpayClearpayRedirect {} | PayLaterData::PayBrightRedirect {} diff --git a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs index 6c47e5d58d4..c284212f0f5 100644 --- a/crates/hyperswitch_connectors/src/connectors/square/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/square/transformers.rs @@ -85,7 +85,6 @@ impl TryFrom<(&types::TokenizationRouterData, PayLaterData)> for SquareTokenRequ PayLaterData::AfterpayClearpayRedirect { .. } | PayLaterData::KlarnaRedirect { .. } | PayLaterData::KlarnaSdk { .. } - | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect { .. } | PayLaterData::PayBrightRedirect { .. } | PayLaterData::WalleyRedirect { .. } diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index a9e21f9071c..14e0b5ba972 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -752,7 +752,6 @@ impl TryFrom<&PayLaterData> for ZenPaymentsRequest { match value { PayLaterData::KlarnaRedirect { .. } | PayLaterData::KlarnaSdk { .. } - | PayLaterData::KlarnaCheckout {} | PayLaterData::AffirmRedirect {} | PayLaterData::AfterpayClearpayRedirect { .. } | PayLaterData::PayBrightRedirect {} diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 418c505cf52..70fe780ea64 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2324,7 +2324,6 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, - KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -2450,7 +2449,6 @@ impl From for PaymentMethodDataType { PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { payment_method_data::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, payment_method_data::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, - payment_method_data::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout, payment_method_data::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, payment_method_data::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 9e83810c9e0..e96368080d9 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -157,7 +157,6 @@ pub enum CardRedirectData { pub enum PayLaterData { KlarnaRedirect {}, KlarnaSdk { token: String }, - KlarnaCheckout {}, AffirmRedirect {}, AfterpayClearpayRedirect {}, PayBrightRedirect {}, @@ -904,7 +903,6 @@ impl From for PayLaterData { match value { api_models::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect {}, api_models::payments::PayLaterData::KlarnaSdk { token } => Self::KlarnaSdk { token }, - api_models::payments::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout {}, api_models::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect {}, api_models::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect {} @@ -1559,7 +1557,6 @@ impl GetPaymentMethodType for PayLaterData { match self { Self::KlarnaRedirect { .. } => api_enums::PaymentMethodType::Klarna, Self::KlarnaSdk { .. } => api_enums::PaymentMethodType::Klarna, - Self::KlarnaCheckout {} => api_enums::PaymentMethodType::Klarna, Self::AffirmRedirect {} => api_enums::PaymentMethodType::Affirm, Self::AfterpayClearpayRedirect { .. } => api_enums::PaymentMethodType::AfterpayClearpay, Self::PayBrightRedirect {} => api_enums::PaymentMethodType::PayBright, diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 421f430e74e..46076b057eb 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -10508,9 +10508,9 @@ impl Default for settings::RequiredFields { RequiredFieldFinal { mandate : HashMap::new(), non_mandate: HashMap::from([ - ( "payment_method_data.pay_later.klarna.billing_country".to_string(), + ( "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "billing_country".to_string(), field_type: enums::FieldType::UserAddressCountry{ options: vec![ @@ -10558,9 +10558,9 @@ impl Default for settings::RequiredFields { mandate : HashMap::new(), non_mandate: HashMap::new(), common : HashMap::from([ - ( "payment_method_data.pay_later.klarna.billing_country".to_string(), + ( "billing.address.country".to_string(), RequiredFieldInfo { - required_field: "payment_method_data.pay_later.klarna.billing_country".to_string(), + required_field: "payment_method_data.billing.address.country".to_string(), display_name: "billing_country".to_string(), field_type: enums::FieldType::UserAddressCountry{ options: vec![ diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index adb1a795f70..1daa1fbaffc 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2351,8 +2351,7 @@ impl check_required_field(billing_address, "billing")?; Ok(AdyenPaymentMethod::Atome) } - domain::payments::PayLaterData::KlarnaCheckout {} - | domain::payments::PayLaterData::KlarnaSdk { .. } => { + domain::payments::PayLaterData::KlarnaSdk { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Adyen"), ) diff --git a/crates/router/src/connector/klarna.rs b/crates/router/src/connector/klarna.rs index 6fbc48a66a0..9b0da0ade6d 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/router/src/connector/klarna.rs @@ -672,7 +672,7 @@ impl })), } } - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaRedirect {}) => { match (payment_experience, payment_method_type) { ( common_enums::PaymentExperience::RedirectToUrl, diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/router/src/connector/klarna/transformers.rs index 3cefa4ec6c9..d1a0b80bad6 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/router/src/connector/klarna/transformers.rs @@ -277,7 +277,7 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP })), } } - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaCheckout {}) => { + domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaRedirect {}) => { match request.order_details.clone() { Some(order_details) => Ok(Self { purchase_country: item.router_data.get_billing_country()?, diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/router/src/connector/nuvei/transformers.rs index eea700f695c..136d0531d9a 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/router/src/connector/nuvei/transformers.rs @@ -978,7 +978,6 @@ where get_pay_later_info(AlternativePaymentMethodType::AfterPay, item) } domain::PayLaterData::KlarnaSdk { .. } - | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 18739851ae8..884e67714ff 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -1274,7 +1274,6 @@ impl TryFrom<&domain::PayLaterData> for PaypalPaymentsRequest { match value { domain::PayLaterData::KlarnaRedirect { .. } | domain::PayLaterData::KlarnaSdk { .. } - | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::AffirmRedirect {} | domain::PayLaterData::AfterpayClearpayRedirect { .. } | domain::PayLaterData::PayBrightRedirect {} diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 363dfb81bcc..a93ba6ecaa5 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1002,7 +1002,6 @@ impl TryFrom<&domain::payments::PayLaterData> for StripePaymentMethodType { } domain::PayLaterData::KlarnaSdk { .. } - | domain::PayLaterData::KlarnaCheckout {} | domain::PayLaterData::PayBrightRedirect {} | domain::PayLaterData::WalleyRedirect {} | domain::PayLaterData::AlmaRedirect {} diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index c4824d65836..eff68176f8c 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -2772,7 +2772,6 @@ pub enum PaymentMethodDataType { SwishQr, KlarnaRedirect, KlarnaSdk, - KlarnaCheckout, AffirmRedirect, AfterpayClearpayRedirect, PayBrightRedirect, @@ -2897,7 +2896,6 @@ impl From for PaymentMethodDataType { domain::payments::PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { domain::payments::PayLaterData::KlarnaRedirect { .. } => Self::KlarnaRedirect, domain::payments::PayLaterData::KlarnaSdk { .. } => Self::KlarnaSdk, - domain::payments::PayLaterData::KlarnaCheckout {} => Self::KlarnaCheckout, domain::payments::PayLaterData::AffirmRedirect {} => Self::AffirmRedirect, domain::payments::PayLaterData::AfterpayClearpayRedirect { .. } => { Self::AfterpayClearpayRedirect From 60ddddf24a1625b8044c095c5d01754022102813 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:16:00 +0530 Subject: [PATCH 055/133] feat(routing): Contract based routing integration (#6761) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .github/CODEOWNERS | 3 + api-reference/openapi_spec.json | 247 +++++++- crates/api_models/src/events/routing.rs | 25 +- crates/api_models/src/routing.rs | 248 ++++++-- crates/external_services/build.rs | 4 +- .../src/grpc_client/dynamic_routing.rs | 27 +- .../contract_routing_client.rs | 192 +++++++ ..._client.rs => elimination_based_client.rs} | 0 crates/openapi/src/openapi.rs | 6 + crates/openapi/src/routes/routing.rs | 58 +- crates/router/src/core/errors.rs | 10 + crates/router/src/core/metrics.rs | 1 + crates/router/src/core/payments.rs | 11 +- .../payments/operations/payment_response.rs | 38 +- crates/router/src/core/payments/routing.rs | 209 ++++++- crates/router/src/core/routing.rs | 346 +++++++++++- crates/router/src/core/routing/helpers.rs | 529 ++++++++++++++---- crates/router/src/routes/app.rs | 17 +- .../routes/metrics/bg_metrics_collector.rs | 3 + crates/router/src/routes/routing.rs | 96 +++- crates/storage_impl/src/redis/cache.rs | 12 + crates/storage_impl/src/redis/pub_sub.rs | 20 +- proto/contract_routing.proto | 76 +++ 23 files changed, 1968 insertions(+), 210 deletions(-) create mode 100644 crates/external_services/src/grpc_client/dynamic_routing/contract_routing_client.rs rename crates/external_services/src/grpc_client/dynamic_routing/{elimination_rate_client.rs => elimination_based_client.rs} (100%) create mode 100644 proto/contract_routing.proto diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 6ad86dd4ccc..16254a6b998 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -67,6 +67,9 @@ crates/router/src/core/routing @juspay/hyperswitch-routing crates/router/src/core/routing.rs @juspay/hyperswitch-routing crates/router/src/core/payments/routing @juspay/hyperswitch-routing crates/router/src/core/payments/routing.rs @juspay/hyperswitch-routing +crates/external_services/src/grpc_client/dynamic_routing.rs @juspay/hyperswitch-routing +crates/external_services/src/grpc_client/dynamic_routing/ @juspay/hyperswitch-routing +crates/external_services/src/grpc_client/health_check_client.rs @juspay/hyperswitch-routing crates/api_models/src/payment_methods.rs @juspay/hyperswitch-routing crates/router/src/core/payment_methods.rs @juspay/hyperswitch-routing diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 6c03d7a9a25..7132fe905c7 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -4082,7 +4082,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/DynamicRoutingFeatures" + "$ref": "#/components/schemas/SuccessBasedRoutingConfig" } } }, @@ -4229,7 +4229,7 @@ { "name": "enable", "in": "query", - "description": "Feature to enable for success based routing", + "description": "Feature to enable for elimination based routing", "required": true, "schema": { "$ref": "#/components/schemas/DynamicRoutingFeatures" @@ -4273,6 +4273,174 @@ ] } }, + "/account/:account_id/business_profile/:profile_id/dynamic_routing/contracts/toggle": { + "post": { + "tags": [ + "Routing" + ], + "summary": "Routing - Toggle Contract routing for profile", + "description": "Create a Contract based dynamic routing algorithm", + "operationId": "Toggle contract routing algorithm", + "parameters": [ + { + "name": "account_id", + "in": "path", + "description": "Merchant id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "profile_id", + "in": "path", + "description": "Profile id under which Dynamic routing needs to be toggled", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "enable", + "in": "query", + "description": "Feature to enable for contract based routing", + "required": true, + "schema": { + "$ref": "#/components/schemas/DynamicRoutingFeatures" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContractBasedRoutingConfig" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Routing Algorithm created", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingDictionaryRecord" + } + } + } + }, + "400": { + "description": "Request body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + }, + { + "jwt_key": [] + } + ] + } + }, + "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/contracts/config/{algorithm_id}": { + "patch": { + "tags": [ + "Routing" + ], + "summary": "Routing - Update contract based dynamic routing config for profile", + "description": "Update contract based dynamic routing algorithm", + "operationId": "Update contract based dynamic routing configs", + "parameters": [ + { + "name": "account_id", + "in": "path", + "description": "Merchant id", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "profile_id", + "in": "path", + "description": "Profile id under which Dynamic routing needs to be toggled", + "required": true, + "schema": { + "type": "string" + } + }, + { + "name": "algorithm_id", + "in": "path", + "description": "Contract based routing algorithm id which was last activated to update the config", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ContractBasedRoutingConfig" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Routing Algorithm updated", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RoutingDictionaryRecord" + } + } + } + }, + "400": { + "description": "Update body is malformed" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Resource missing" + }, + "422": { + "description": "Unprocessable request" + }, + "500": { + "description": "Internal server error" + } + }, + "security": [ + { + "api_key": [] + }, + { + "jwt_key": [] + } + ] + } + }, "/blocklist": { "delete": { "tags": [ @@ -9536,6 +9704,54 @@ }, "additionalProperties": false }, + "ContractBasedRoutingConfig": { + "type": "object", + "properties": { + "config": { + "allOf": [ + { + "$ref": "#/components/schemas/ContractBasedRoutingConfigBody" + } + ], + "nullable": true + }, + "label_info": { + "type": "array", + "items": { + "$ref": "#/components/schemas/LabelInformation" + }, + "nullable": true + } + } + }, + "ContractBasedRoutingConfigBody": { + "type": "object", + "properties": { + "constants": { + "type": "array", + "items": { + "type": "number", + "format": "double" + }, + "nullable": true + }, + "time_scale": { + "allOf": [ + { + "$ref": "#/components/schemas/ContractBasedTimeScale" + } + ], + "nullable": true + } + } + }, + "ContractBasedTimeScale": { + "type": "string", + "enum": [ + "day", + "month" + ] + }, "CountryAlpha2": { "type": "string", "enum": [ @@ -12886,6 +13102,33 @@ } } }, + "LabelInformation": { + "type": "object", + "required": [ + "label", + "target_count", + "target_time", + "mca_id" + ], + "properties": { + "label": { + "type": "string" + }, + "target_count": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "target_time": { + "type": "integer", + "format": "int64", + "minimum": 0 + }, + "mca_id": { + "type": "string" + } + } + }, "LinkedRoutingConfigRetrieveResponse": { "oneOf": [ { diff --git a/crates/api_models/src/events/routing.rs b/crates/api_models/src/events/routing.rs index f3e169336bf..122d1f8d3c8 100644 --- a/crates/api_models/src/events/routing.rs +++ b/crates/api_models/src/events/routing.rs @@ -1,12 +1,13 @@ use common_utils::events::{ApiEventMetric, ApiEventsType}; use crate::routing::{ - LinkedRoutingConfigRetrieveResponse, MerchantRoutingAlgorithm, ProfileDefaultRoutingConfig, - RoutingAlgorithmId, RoutingConfigRequest, RoutingDictionaryRecord, RoutingKind, - RoutingLinkWrapper, RoutingPayloadWrapper, RoutingRetrieveLinkQuery, + ContractBasedRoutingPayloadWrapper, ContractBasedRoutingSetupPayloadWrapper, + DynamicRoutingUpdateConfigQuery, LinkedRoutingConfigRetrieveResponse, MerchantRoutingAlgorithm, + ProfileDefaultRoutingConfig, RoutingAlgorithmId, RoutingConfigRequest, RoutingDictionaryRecord, + RoutingKind, RoutingLinkWrapper, RoutingPayloadWrapper, RoutingRetrieveLinkQuery, RoutingRetrieveLinkQueryWrapper, RoutingRetrieveQuery, RoutingVolumeSplitWrapper, - SuccessBasedRoutingConfig, SuccessBasedRoutingPayloadWrapper, - SuccessBasedRoutingUpdateConfigQuery, ToggleDynamicRoutingQuery, ToggleDynamicRoutingWrapper, + SuccessBasedRoutingConfig, SuccessBasedRoutingPayloadWrapper, ToggleDynamicRoutingQuery, + ToggleDynamicRoutingWrapper, }; impl ApiEventMetric for RoutingKind { @@ -97,13 +98,25 @@ impl ApiEventMetric for SuccessBasedRoutingPayloadWrapper { } } +impl ApiEventMetric for ContractBasedRoutingPayloadWrapper { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Routing) + } +} + +impl ApiEventMetric for ContractBasedRoutingSetupPayloadWrapper { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::Routing) + } +} + impl ApiEventMetric for ToggleDynamicRoutingWrapper { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } } -impl ApiEventMetric for SuccessBasedRoutingUpdateConfigQuery { +impl ApiEventMetric for DynamicRoutingUpdateConfigQuery { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Routing) } diff --git a/crates/api_models/src/routing.rs b/crates/api_models/src/routing.rs index 8e502767575..d4b4f76e178 100644 --- a/crates/api_models/src/routing.rs +++ b/crates/api_models/src/routing.rs @@ -513,17 +513,27 @@ pub struct RoutingLinkWrapper { pub algorithm_id: RoutingAlgorithmId, } -#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicAlgorithmWithTimestamp { pub algorithm_id: Option, pub timestamp: i64, } +impl DynamicAlgorithmWithTimestamp { + pub fn new(algorithm_id: Option) -> Self { + Self { + algorithm_id, + timestamp: common_utils::date_time::now_unix_timestamp(), + } + } +} + #[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize)] pub struct DynamicRoutingAlgorithmRef { pub success_based_algorithm: Option, pub dynamic_routing_volume_split: Option, pub elimination_routing_algorithm: Option, + pub contract_based_routing: Option, } pub trait DynamicRoutingAlgoAccessor { @@ -555,6 +565,43 @@ impl DynamicRoutingAlgoAccessor for EliminationRoutingAlgorithm { } } +impl DynamicRoutingAlgoAccessor for ContractRoutingAlgorithm { + fn get_algorithm_id_with_timestamp( + self, + ) -> DynamicAlgorithmWithTimestamp { + self.algorithm_id_with_timestamp + } + fn get_enabled_features(&mut self) -> &mut DynamicRoutingFeatures { + &mut self.enabled_feature + } +} + +impl EliminationRoutingAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + +impl SuccessBasedAlgorithm { + pub fn new( + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< + common_utils::id_type::RoutingId, + >, + ) -> Self { + Self { + algorithm_id_with_timestamp, + enabled_feature: DynamicRoutingFeatures::None, + } + } +} + impl DynamicRoutingAlgorithmRef { pub fn update(&mut self, new: Self) { if let Some(elimination_routing_algorithm) = new.elimination_routing_algorithm { @@ -563,9 +610,12 @@ impl DynamicRoutingAlgorithmRef { if let Some(success_based_algorithm) = new.success_based_algorithm { self.success_based_algorithm = Some(success_based_algorithm) } + if let Some(contract_based_routing) = new.contract_based_routing { + self.contract_based_routing = Some(contract_based_routing) + } } - pub fn update_specific_ref( + pub fn update_enabled_features( &mut self, algo_type: DynamicRoutingType, feature_to_enable: DynamicRoutingFeatures, @@ -581,6 +631,11 @@ impl DynamicRoutingAlgorithmRef { .as_mut() .map(|algo| algo.enabled_feature = feature_to_enable); } + DynamicRoutingType::ContractBasedRouting => { + self.contract_based_routing + .as_mut() + .map(|algo| algo.enabled_feature = feature_to_enable); + } } } @@ -589,32 +644,6 @@ impl DynamicRoutingAlgorithmRef { } } -impl EliminationRoutingAlgorithm { - pub fn new( - algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< - common_utils::id_type::RoutingId, - >, - ) -> Self { - Self { - algorithm_id_with_timestamp, - enabled_feature: DynamicRoutingFeatures::None, - } - } -} - -impl SuccessBasedAlgorithm { - pub fn new( - algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp< - common_utils::id_type::RoutingId, - >, - ) -> Self { - Self { - algorithm_id_with_timestamp, - enabled_feature: DynamicRoutingFeatures::None, - } - } -} - #[derive(Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize)] pub struct RoutingVolumeSplit { pub routing_type: RoutingType, @@ -648,6 +677,14 @@ pub struct SuccessBasedAlgorithm { pub enabled_feature: DynamicRoutingFeatures, } +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ContractRoutingAlgorithm { + pub algorithm_id_with_timestamp: + DynamicAlgorithmWithTimestamp, + #[serde(default)] + pub enabled_feature: DynamicRoutingFeatures, +} + #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct EliminationRoutingAlgorithm { pub algorithm_id_with_timestamp: @@ -678,24 +715,53 @@ impl DynamicRoutingAlgorithmRef { match dynamic_routing_type { DynamicRoutingType::SuccessRateBasedRouting => { self.success_based_algorithm = Some(SuccessBasedAlgorithm { - algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }, + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)), enabled_feature, }) } DynamicRoutingType::EliminationRouting => { self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm { - algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp { - algorithm_id: Some(new_id), - timestamp: common_utils::date_time::now_unix_timestamp(), - }, + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)), + enabled_feature, + }) + } + DynamicRoutingType::ContractBasedRouting => { + self.contract_based_routing = Some(ContractRoutingAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(Some(new_id)), enabled_feature, }) } }; } + + pub fn disable_algorithm_id(&mut self, dynamic_routing_type: DynamicRoutingType) { + match dynamic_routing_type { + DynamicRoutingType::SuccessRateBasedRouting => { + if let Some(success_based_algo) = &self.success_based_algorithm { + self.success_based_algorithm = Some(SuccessBasedAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None), + enabled_feature: success_based_algo.enabled_feature, + }); + } + } + DynamicRoutingType::EliminationRouting => { + if let Some(elimination_based_algo) = &self.elimination_routing_algorithm { + self.elimination_routing_algorithm = Some(EliminationRoutingAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None), + enabled_feature: elimination_based_algo.enabled_feature, + }); + } + } + DynamicRoutingType::ContractBasedRouting => { + if let Some(contract_based_algo) = &self.contract_based_routing { + self.contract_based_routing = Some(ContractRoutingAlgorithm { + algorithm_id_with_timestamp: DynamicAlgorithmWithTimestamp::new(None), + enabled_feature: contract_based_algo.enabled_feature, + }); + } + } + } + } } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] @@ -708,7 +774,9 @@ pub struct DynamicRoutingVolumeSplitQuery { pub split: u8, } -#[derive(Debug, Default, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] +#[derive( + Debug, Default, Clone, Copy, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema, +)] #[serde(rename_all = "snake_case")] pub enum DynamicRoutingFeatures { Metrics, @@ -718,7 +786,7 @@ pub enum DynamicRoutingFeatures { } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] -pub struct SuccessBasedRoutingUpdateConfigQuery { +pub struct DynamicRoutingUpdateConfigQuery { #[schema(value_type = String)] pub algorithm_id: common_utils::id_type::RoutingId, #[schema(value_type = String)] @@ -827,10 +895,27 @@ pub struct SuccessBasedRoutingPayloadWrapper { pub profile_id: common_utils::id_type::ProfileId, } -#[derive(Debug, Clone, strum::Display, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ContractBasedRoutingPayloadWrapper { + pub updated_config: ContractBasedRoutingConfig, + pub algorithm_id: common_utils::id_type::RoutingId, + pub profile_id: common_utils::id_type::ProfileId, +} + +#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +pub struct ContractBasedRoutingSetupPayloadWrapper { + pub config: Option, + pub profile_id: common_utils::id_type::ProfileId, + pub features_to_enable: DynamicRoutingFeatures, +} + +#[derive( + Debug, Clone, Copy, strum::Display, serde::Serialize, serde::Deserialize, PartialEq, Eq, +)] pub enum DynamicRoutingType { SuccessRateBasedRouting, EliminationRouting, + ContractBasedRouting, } impl SuccessBasedRoutingConfig { @@ -872,6 +957,91 @@ impl CurrentBlockThreshold { } } +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct ContractBasedRoutingConfig { + pub config: Option, + pub label_info: Option>, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct ContractBasedRoutingConfigBody { + pub constants: Option>, + pub time_scale: Option, +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub struct LabelInformation { + pub label: String, + pub target_count: u64, + pub target_time: u64, + #[schema(value_type = String)] + pub mca_id: common_utils::id_type::MerchantConnectorAccountId, +} + +impl LabelInformation { + pub fn update_target_time(&mut self, new: &Self) { + self.target_time = new.target_time; + } + + pub fn update_target_count(&mut self, new: &Self) { + self.target_count = new.target_count; + } +} + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ContractBasedTimeScale { + Day, + Month, +} + +impl Default for ContractBasedRoutingConfig { + fn default() -> Self { + Self { + config: Some(ContractBasedRoutingConfigBody { + constants: Some(vec![0.7, 0.35]), + time_scale: Some(ContractBasedTimeScale::Day), + }), + label_info: None, + } + } +} + +impl ContractBasedRoutingConfig { + pub fn update(&mut self, new: Self) { + if let Some(new_config) = new.config { + self.config.as_mut().map(|config| config.update(new_config)); + } + if let Some(new_label_info) = new.label_info { + new_label_info.iter().for_each(|new_label_info| { + if let Some(existing_label_infos) = &mut self.label_info { + for existing_label_info in existing_label_infos { + if existing_label_info.mca_id == new_label_info.mca_id { + existing_label_info.update_target_time(new_label_info); + existing_label_info.update_target_count(new_label_info); + } + } + } else { + self.label_info = Some(vec![new_label_info.clone()]); + } + }); + } + } +} + +impl ContractBasedRoutingConfigBody { + pub fn update(&mut self, new: Self) { + if let Some(new_cons) = new.constants { + self.constants = Some(new_cons) + } + if let Some(new_time_scale) = new.time_scale { + self.time_scale = Some(new_time_scale) + } + } +} #[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] pub struct RoutableConnectorChoiceWithBucketName { pub routable_connector_choice: RoutableConnectorChoice, diff --git a/crates/external_services/build.rs b/crates/external_services/build.rs index b3fa1a8fed2..8dba0bb3e98 100644 --- a/crates/external_services/build.rs +++ b/crates/external_services/build.rs @@ -6,6 +6,7 @@ fn main() -> Result<(), Box> { let proto_path = router_env::workspace_path().join("proto"); let success_rate_proto_file = proto_path.join("success_rate.proto"); + let contract_routing_proto_file = proto_path.join("contract_routing.proto"); let elimination_proto_file = proto_path.join("elimination_rate.proto"); let health_check_proto_file = proto_path.join("health_check.proto"); let out_dir = std::path::PathBuf::from(std::env::var("OUT_DIR")?); @@ -16,8 +17,9 @@ fn main() -> Result<(), Box> { .compile( &[ success_rate_proto_file, - elimination_proto_file, health_check_proto_file, + elimination_proto_file, + contract_routing_proto_file, ], &[proto_path], ) diff --git a/crates/external_services/src/grpc_client/dynamic_routing.rs b/crates/external_services/src/grpc_client/dynamic_routing.rs index e5050227fa8..e34f721b30e 100644 --- a/crates/external_services/src/grpc_client/dynamic_routing.rs +++ b/crates/external_services/src/grpc_client/dynamic_routing.rs @@ -1,14 +1,18 @@ +/// Module for Contract based routing +pub mod contract_routing_client; + use std::fmt::Debug; use common_utils::errors::CustomResult; use router_env::logger; use serde; /// Elimination Routing Client Interface Implementation -pub mod elimination_rate_client; +pub mod elimination_based_client; /// Success Routing Client Interface Implementation pub mod success_rate_client; -pub use elimination_rate_client::EliminationAnalyserClient; +pub use contract_routing_client::ContractScoreCalculatorClient; +pub use elimination_based_client::EliminationAnalyserClient; pub use success_rate_client::SuccessRateCalculatorClient; use super::Client; @@ -27,6 +31,10 @@ pub enum DynamicRoutingError { /// Error from Dynamic Routing Server while performing success_rate analysis #[error("Error from Dynamic Routing Server while perfrming success_rate analysis : {0}")] SuccessRateBasedRoutingFailure(String), + + /// Error from Dynamic Routing Server while performing contract based routing + #[error("Error from Dynamic Routing Server while performing contract based routing: {0}")] + ContractBasedRoutingFailure(String), /// Error from Dynamic Routing Server while perfrming elimination #[error("Error from Dynamic Routing Server while perfrming elimination : {0}")] EliminationRateRoutingFailure(String), @@ -37,8 +45,10 @@ pub enum DynamicRoutingError { pub struct RoutingStrategy { /// success rate service for Dynamic Routing pub success_rate_client: Option>, + /// contract based routing service for Dynamic Routing + pub contract_based_client: Option>, /// elimination service for Dynamic Routing - pub elimination_rate_client: Option>, + pub elimination_based_client: Option>, } /// Contains the Dynamic Routing Client Config @@ -65,7 +75,7 @@ impl DynamicRoutingClientConfig { self, client: Client, ) -> Result> { - let (success_rate_client, elimination_rate_client) = match self { + let (success_rate_client, contract_based_client, elimination_based_client) = match self { Self::Enabled { host, port, .. } => { let uri = format!("http://{}:{}", host, port).parse::()?; logger::info!("Connection established with dynamic routing gRPC Server"); @@ -74,14 +84,19 @@ impl DynamicRoutingClientConfig { client.clone(), uri.clone(), )), + Some(ContractScoreCalculatorClient::with_origin( + client.clone(), + uri.clone(), + )), Some(EliminationAnalyserClient::with_origin(client, uri)), ) } - Self::Disabled => (None, None), + Self::Disabled => (None, None, None), }; Ok(RoutingStrategy { success_rate_client, - elimination_rate_client, + contract_based_client, + elimination_based_client, }) } } diff --git a/crates/external_services/src/grpc_client/dynamic_routing/contract_routing_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/contract_routing_client.rs new file mode 100644 index 00000000000..b210d996bc0 --- /dev/null +++ b/crates/external_services/src/grpc_client/dynamic_routing/contract_routing_client.rs @@ -0,0 +1,192 @@ +use api_models::routing::{ + ContractBasedRoutingConfig, ContractBasedRoutingConfigBody, ContractBasedTimeScale, + LabelInformation, RoutableConnectorChoice, RoutableConnectorChoiceWithStatus, +}; +use common_utils::{ + ext_traits::OptionExt, + transformers::{ForeignFrom, ForeignTryFrom}, +}; +pub use contract_routing::{ + contract_score_calculator_client::ContractScoreCalculatorClient, CalContractScoreConfig, + CalContractScoreRequest, CalContractScoreResponse, InvalidateContractRequest, + InvalidateContractResponse, LabelInformation as ProtoLabelInfo, TimeScale, + UpdateContractRequest, UpdateContractResponse, +}; +use error_stack::ResultExt; +use router_env::logger; + +use crate::grpc_client::{self, GrpcHeaders}; +#[allow( + missing_docs, + unused_qualifications, + clippy::unwrap_used, + clippy::as_conversions, + clippy::use_self +)] +pub mod contract_routing { + tonic::include_proto!("contract_routing"); +} +use super::{Client, DynamicRoutingError, DynamicRoutingResult}; +/// The trait ContractBasedDynamicRouting would have the functions required to support the calculation and updation window +#[async_trait::async_trait] +pub trait ContractBasedDynamicRouting: dyn_clone::DynClone + Send + Sync { + /// To calculate the contract scores for the list of chosen connectors + async fn calculate_contract_score( + &self, + id: String, + config: ContractBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To update the contract scores with the given labels + async fn update_contracts( + &self, + id: String, + label_info: Vec, + params: String, + response: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; + /// To invalidates the contract scores against the id + async fn invalidate_contracts( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult; +} + +#[async_trait::async_trait] +impl ContractBasedDynamicRouting for ContractScoreCalculatorClient { + async fn calculate_contract_score( + &self, + id: String, + config: ContractBasedRoutingConfig, + params: String, + label_input: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let labels = label_input + .into_iter() + .map(|conn_choice| conn_choice.to_string()) + .collect::>(); + + let config = config + .config + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + + let request = grpc_client::create_grpc_request( + CalContractScoreRequest { + id, + params, + labels, + config, + }, + headers, + ); + + let response = self + .clone() + .fetch_contract_score(request) + .await + .change_context(DynamicRoutingError::ContractBasedRoutingFailure( + "Failed to fetch the contract score".to_string(), + ))? + .into_inner(); + + logger::info!(dynamic_routing_response=?response); + + Ok(response) + } + + async fn update_contracts( + &self, + id: String, + label_info: Vec, + params: String, + _response: Vec, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let labels_information = label_info + .into_iter() + .map(ProtoLabelInfo::foreign_from) + .collect::>(); + + let request = grpc_client::create_grpc_request( + UpdateContractRequest { + id, + params, + labels_information, + }, + headers, + ); + + let response = self + .clone() + .update_contract(request) + .await + .change_context(DynamicRoutingError::ContractBasedRoutingFailure( + "Failed to update the contracts".to_string(), + ))? + .into_inner(); + + logger::info!(dynamic_routing_response=?response); + + Ok(response) + } + async fn invalidate_contracts( + &self, + id: String, + headers: GrpcHeaders, + ) -> DynamicRoutingResult { + let request = grpc_client::create_grpc_request(InvalidateContractRequest { id }, headers); + + let response = self + .clone() + .invalidate_contract(request) + .await + .change_context(DynamicRoutingError::ContractBasedRoutingFailure( + "Failed to invalidate the contracts".to_string(), + ))? + .into_inner(); + Ok(response) + } +} + +impl ForeignFrom for TimeScale { + fn foreign_from(scale: ContractBasedTimeScale) -> Self { + Self { + time_scale: match scale { + ContractBasedTimeScale::Day => 0, + _ => 1, + }, + } + } +} + +impl ForeignTryFrom for CalContractScoreConfig { + type Error = error_stack::Report; + fn foreign_try_from(config: ContractBasedRoutingConfigBody) -> Result { + Ok(Self { + constants: config + .constants + .get_required_value("constants") + .change_context(DynamicRoutingError::MissingRequiredField { + field: "constants".to_string(), + })?, + time_scale: config.time_scale.clone().map(TimeScale::foreign_from), + }) + } +} + +impl ForeignFrom for ProtoLabelInfo { + fn foreign_from(config: LabelInformation) -> Self { + Self { + label: config.label, + target_count: config.target_count, + target_time: config.target_time, + current_count: 1, + } + } +} diff --git a/crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs b/crates/external_services/src/grpc_client/dynamic_routing/elimination_based_client.rs similarity index 100% rename from crates/external_services/src/grpc_client/dynamic_routing/elimination_rate_client.rs rename to crates/external_services/src/grpc_client/dynamic_routing/elimination_based_client.rs diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 7102c23d17e..026eae764c4 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -166,6 +166,8 @@ Never share your secret api keys. Keep them guarded and secure. routes::routing::success_based_routing_update_configs, routes::routing::toggle_success_based_routing, routes::routing::toggle_elimination_routing, + routes::routing::contract_based_routing_setup_config, + routes::routing::contract_based_routing_update_configs, // Routes for blocklist routes::blocklist::remove_entry_from_blocklist, @@ -615,6 +617,10 @@ Never share your secret api keys. Keep them guarded and secure. api_models::routing::DynamicRoutingConfigParams, api_models::routing::CurrentBlockThreshold, api_models::routing::SuccessBasedRoutingConfigBody, + api_models::routing::ContractBasedRoutingConfig, + api_models::routing::ContractBasedRoutingConfigBody, + api_models::routing::LabelInformation, + api_models::routing::ContractBasedTimeScale, api_models::routing::LinkedRoutingConfigRetrieveResponse, api_models::routing::RoutingRetrieveResponse, api_models::routing::ProfileDefaultRoutingConfig, diff --git a/crates/openapi/src/routes/routing.rs b/crates/openapi/src/routes/routing.rs index 6b968f80172..c21669a9932 100644 --- a/crates/openapi/src/routes/routing.rs +++ b/crates/openapi/src/routes/routing.rs @@ -292,7 +292,7 @@ pub async fn toggle_success_based_routing() {} ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), ("algorithm_id" = String, Path, description = "Success based routing algorithm id which was last activated to update the config"), ), - request_body = DynamicRoutingFeatures, + request_body = SuccessBasedRoutingConfig, responses( (status = 200, description = "Routing Algorithm updated", body = RoutingDictionaryRecord), (status = 400, description = "Update body is malformed"), @@ -317,7 +317,7 @@ pub async fn success_based_routing_update_configs() {} params( ("account_id" = String, Path, description = "Merchant id"), ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), - ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for success based routing"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for elimination based routing"), ), responses( (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), @@ -332,3 +332,57 @@ pub async fn success_based_routing_update_configs() {} security(("api_key" = []), ("jwt_key" = [])) )] pub async fn toggle_elimination_routing() {} + +#[cfg(feature = "v1")] +/// Routing - Toggle Contract routing for profile +/// +/// Create a Contract based dynamic routing algorithm +#[utoipa::path( + post, + path = "/account/:account_id/business_profile/:profile_id/dynamic_routing/contracts/toggle", + params( + ("account_id" = String, Path, description = "Merchant id"), + ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), + ("enable" = DynamicRoutingFeatures, Query, description = "Feature to enable for contract based routing"), + ), + request_body = ContractBasedRoutingConfig, + responses( + (status = 200, description = "Routing Algorithm created", body = RoutingDictionaryRecord), + (status = 400, description = "Request body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Toggle contract routing algorithm", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub async fn contract_based_routing_setup_config() {} + +#[cfg(feature = "v1")] +/// Routing - Update contract based dynamic routing config for profile +/// +/// Update contract based dynamic routing algorithm +#[utoipa::path( + patch, + path = "/account/{account_id}/business_profile/{profile_id}/dynamic_routing/contracts/config/{algorithm_id}", + params( + ("account_id" = String, Path, description = "Merchant id"), + ("profile_id" = String, Path, description = "Profile id under which Dynamic routing needs to be toggled"), + ("algorithm_id" = String, Path, description = "Contract based routing algorithm id which was last activated to update the config"), + ), + request_body = ContractBasedRoutingConfig, + responses( + (status = 200, description = "Routing Algorithm updated", body = RoutingDictionaryRecord), + (status = 400, description = "Update body is malformed"), + (status = 500, description = "Internal server error"), + (status = 404, description = "Resource missing"), + (status = 422, description = "Unprocessable request"), + (status = 403, description = "Forbidden"), + ), + tag = "Routing", + operation_id = "Update contract based dynamic routing configs", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub async fn contract_based_routing_update_configs() {} diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index b4c1c1c2c03..54cae42ceb3 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -398,6 +398,16 @@ pub enum RoutingError { GenericNotFoundError { field: String }, #[error("Unable to deserialize from '{from}' to '{to}'")] DeserializationError { from: String, to: String }, + #[error("Unable to retrieve contract based routing config")] + ContractBasedRoutingConfigError, + #[error("Params not found in contract based routing config")] + ContractBasedRoutingParamsNotFoundError, + #[error("Unable to calculate contract score from dynamic routing service")] + ContractScoreCalculationError, + #[error("contract routing client from dynamic routing gRPC service not initialized")] + ContractRoutingClientInitializationError, + #[error("Invalid contract based connector label received from dynamic routing service: '{0}'")] + InvalidContractBasedConnectorLabel(String), } #[derive(Debug, Clone, thiserror::Error)] diff --git a/crates/router/src/core/metrics.rs b/crates/router/src/core/metrics.rs index a98a4ffb259..efb463b3a37 100644 --- a/crates/router/src/core/metrics.rs +++ b/crates/router/src/core/metrics.rs @@ -82,6 +82,7 @@ counter_metric!( GLOBAL_METER ); counter_metric!(DYNAMIC_SUCCESS_BASED_ROUTING, GLOBAL_METER); +counter_metric!(DYNAMIC_CONTRACT_BASED_ROUTING, GLOBAL_METER); #[cfg(feature = "partial-auth")] counter_metric!(PARTIAL_AUTH_FAILURE, GLOBAL_METER); diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index dce89107afd..f86072fdcc2 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -6599,8 +6599,8 @@ where .attach_printable("failed to perform volume split on routing type")?; if routing_choice.routing_type.is_dynamic_routing() { - let success_based_routing_config_params_interpolator = - routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new( + let dynamic_routing_config_params_interpolator = + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( payment_data.get_payment_attempt().payment_method, payment_data.get_payment_attempt().payment_method_type, payment_data.get_payment_attempt().authentication_type, @@ -6630,14 +6630,15 @@ where .and_then(|card_isin| card_isin.as_str()) .map(|card_isin| card_isin.to_string()), ); - routing::perform_success_based_routing( + + routing::perform_dynamic_routing( state, connectors.clone(), business_profile, - success_based_routing_config_params_interpolator, + dynamic_routing_config_params_interpolator, ) .await - .map_err(|e| logger::error!(success_rate_routing_error=?e)) + .map_err(|e| logger::error!(dynamic_routing_error=?e)) .unwrap_or(connectors) } else { connectors diff --git a/crates/router/src/core/payments/operations/payment_response.rs b/crates/router/src/core/payments/operations/payment_response.rs index 70d5f79845b..516cebe24d7 100644 --- a/crates/router/src/core/payments/operations/payment_response.rs +++ b/crates/router/src/core/payments/operations/payment_response.rs @@ -5,6 +5,8 @@ use api_models::payments::{ConnectorMandateReferenceId, MandateReferenceId}; use api_models::routing::RoutableConnectorChoice; use async_trait::async_trait; use common_enums::{AuthorizationStatus, SessionUpdateStatus}; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use common_utils::ext_traits::ValueExt; use common_utils::{ ext_traits::{AsyncExt, Encode}, types::{keymanager::KeyManagerState, ConnectorTransactionId, MinorUnit}, @@ -1961,11 +1963,22 @@ async fn payment_response_update_tracker( if payment_intent.status.is_in_terminal_state() && business_profile.dynamic_routing_algorithm.is_some() { + let dynamic_routing_algo_ref: api_models::routing::DynamicRoutingAlgorithmRef = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("DynamicRoutingAlgorithmRef not found in profile")?; + let state = state.clone(); - let business_profile = business_profile.clone(); + let profile_id = business_profile.get_id().to_owned(); let payment_attempt = payment_attempt.clone(); - let success_based_routing_config_params_interpolator = - routing_helpers::SuccessBasedRoutingConfigParamsInterpolator::new( + let dynamic_routing_config_params_interpolator = + routing_helpers::DynamicRoutingConfigParamsInterpolator::new( payment_attempt.payment_method, payment_attempt.payment_method_type, payment_attempt.authentication_type, @@ -1997,14 +2010,27 @@ async fn payment_response_update_tracker( tokio::spawn( async move { routing_helpers::push_metrics_with_update_window_for_success_based_routing( + &state, + &payment_attempt, + routable_connectors.clone(), + &profile_id, + dynamic_routing_algo_ref.clone(), + dynamic_routing_config_params_interpolator.clone(), + ) + .await + .map_err(|e| logger::error!(success_based_routing_metrics_error=?e)) + .ok(); + + routing_helpers::push_metrics_with_update_window_for_contract_based_routing( &state, &payment_attempt, routable_connectors, - &business_profile, - success_based_routing_config_params_interpolator, + &profile_id, + dynamic_routing_algo_ref, + dynamic_routing_config_params_interpolator, ) .await - .map_err(|e| logger::error!(dynamic_routing_metrics_error=?e)) + .map_err(|e| logger::error!(contract_based_routing_metrics_error=?e)) .ok(); } .in_current_span(), diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index dd592bc3064..41be7e6e2d3 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -14,6 +14,8 @@ use api_models::{ enums::{self as api_enums, CountryAlpha2}, routing::ConnectorSelection, }; +#[cfg(feature = "dynamic_routing")] +use common_utils::ext_traits::AsyncExt; use diesel_models::enums as storage_enums; use error_stack::ResultExt; use euclid::{ @@ -23,8 +25,9 @@ use euclid::{ frontend::{ast, dir as euclid_dir}, }; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use external_services::grpc_client::dynamic_routing::success_rate_client::{ - CalSuccessRateResponse, SuccessBasedDynamicRouting, +use external_services::grpc_client::dynamic_routing::{ + contract_routing_client::{CalContractScoreResponse, ContractBasedDynamicRouting}, + success_rate_client::{CalSuccessRateResponse, SuccessBasedDynamicRouting}, }; use hyperswitch_domain_models::address::Address; use kgraph_utils::{ @@ -1281,41 +1284,93 @@ pub fn make_dsl_input_for_surcharge( Ok(backend_input) } -/// success based dynamic routing #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -#[instrument(skip_all)] -pub async fn perform_success_based_routing( +pub async fn perform_dynamic_routing( state: &SessionState, routable_connectors: Vec, - business_profile: &domain::Profile, - success_based_routing_config_params_interpolator: routing::helpers::SuccessBasedRoutingConfigParamsInterpolator, + profile: &domain::Profile, + dynamic_routing_config_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, ) -> RoutingResult> { - let success_based_dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::RoutingError::DeserializationError { - from: "JSON".to_string(), - to: "DynamicRoutingAlgorithmRef".to_string(), - }) - .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? - .unwrap_or_default(); + let dynamic_routing_algo_ref: api_routing::DynamicRoutingAlgorithmRef = profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::RoutingError::DeserializationError { + from: "JSON".to_string(), + to: "DynamicRoutingAlgorithmRef".to_string(), + }) + .attach_printable("unable to deserialize DynamicRoutingAlgorithmRef from JSON")? + .ok_or(errors::RoutingError::GenericNotFoundError { + field: "dynamic_routing_algorithm".to_string(), + })?; + + logger::debug!( + "performing dynamic_routing for profile {}", + profile.get_id().get_string_repr() + ); - let success_based_algo_ref = success_based_dynamic_routing_algo_ref + let connector_list = match dynamic_routing_algo_ref .success_based_algorithm - .ok_or(errors::RoutingError::GenericNotFoundError { field: "success_based_algorithm".to_string() }) - .attach_printable( - "success_based_algorithm not found in dynamic_routing_algorithm from business_profile table", - )?; + .as_ref() + .async_map(|algorithm| { + perform_success_based_routing( + state, + routable_connectors.clone(), + profile.get_id(), + dynamic_routing_config_params_interpolator.clone(), + algorithm.clone(), + ) + }) + .await + .transpose() + .inspect_err(|e| logger::error!(dynamic_routing_error=?e)) + .ok() + .flatten() + { + Some(success_based_list) => success_based_list, + None => { + // Only run contract based if success based returns None + dynamic_routing_algo_ref + .contract_based_routing + .as_ref() + .async_map(|algorithm| { + perform_contract_based_routing( + state, + routable_connectors.clone(), + profile.get_id(), + dynamic_routing_config_params_interpolator, + algorithm.clone(), + ) + }) + .await + .transpose() + .inspect_err(|e| logger::error!(dynamic_routing_error=?e)) + .ok() + .flatten() + .unwrap_or(routable_connectors) + } + }; + Ok(connector_list) +} + +/// success based dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn perform_success_based_routing( + state: &SessionState, + routable_connectors: Vec, + profile_id: &common_utils::id_type::ProfileId, + success_based_routing_config_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, + success_based_algo_ref: api_routing::SuccessBasedAlgorithm, +) -> RoutingResult> { if success_based_algo_ref.enabled_feature == api_routing::DynamicRoutingFeatures::DynamicConnectorSelection { logger::debug!( "performing success_based_routing for profile {}", - business_profile.get_id().get_string_repr() + profile_id.get_string_repr() ); let client = state .grpc_client @@ -1325,18 +1380,18 @@ pub async fn perform_success_based_routing( .ok_or(errors::RoutingError::SuccessRateClientInitializationError) .attach_printable("success_rate gRPC client not found")?; - let success_based_routing_configs = routing::helpers::fetch_success_based_routing_configs( + let success_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs::< + api_routing::SuccessBasedRoutingConfig, + >( state, - business_profile, + profile_id, success_based_algo_ref .algorithm_id_with_timestamp .algorithm_id .ok_or(errors::RoutingError::GenericNotFoundError { field: "success_based_routing_algorithm_id".to_string(), }) - .attach_printable( - "success_based_routing_algorithm_id not found in business_profile", - )?, + .attach_printable("success_based_routing_algorithm_id not found in profile_id")?, ) .await .change_context(errors::RoutingError::SuccessBasedRoutingConfigError) @@ -1352,7 +1407,7 @@ pub async fn perform_success_based_routing( let success_based_connectors: CalSuccessRateResponse = client .calculate_success_rate( - business_profile.get_id().get_string_repr().into(), + profile_id.get_string_repr().into(), success_based_routing_configs, success_based_routing_config_params, routable_connectors, @@ -1398,3 +1453,95 @@ pub async fn perform_success_based_routing( Ok(routable_connectors) } } + +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn perform_contract_based_routing( + state: &SessionState, + routable_connectors: Vec, + profile_id: &common_utils::id_type::ProfileId, + _dynamic_routing_config_params_interpolator: routing::helpers::DynamicRoutingConfigParamsInterpolator, + contract_based_algo_ref: api_routing::ContractRoutingAlgorithm, +) -> RoutingResult> { + if contract_based_algo_ref.enabled_feature + == api_routing::DynamicRoutingFeatures::DynamicConnectorSelection + { + logger::debug!( + "performing contract_based_routing for profile {}", + profile_id.get_string_repr() + ); + let client = state + .grpc_client + .dynamic_routing + .contract_based_client + .as_ref() + .ok_or(errors::RoutingError::ContractRoutingClientInitializationError) + .attach_printable("contract routing gRPC client not found")?; + + let contract_based_routing_configs = routing::helpers::fetch_dynamic_routing_configs::< + api_routing::ContractBasedRoutingConfig, + >( + state, + profile_id, + contract_based_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::RoutingError::GenericNotFoundError { + field: "contract_based_routing_algorithm_id".to_string(), + }) + .attach_printable("contract_based_routing_algorithm_id not found in profile_id")?, + ) + .await + .change_context(errors::RoutingError::ContractBasedRoutingConfigError) + .attach_printable("unable to fetch contract based dynamic routing configs")?; + + let contract_based_connectors: CalContractScoreResponse = client + .calculate_contract_score( + profile_id.get_string_repr().into(), + contract_based_routing_configs, + "".to_string(), + routable_connectors, + state.get_grpc_headers(), + ) + .await + .change_context(errors::RoutingError::ContractScoreCalculationError) + .attach_printable( + "unable to calculate/fetch contract score from dynamic routing service", + )?; + + let mut connectors = Vec::with_capacity(contract_based_connectors.labels_with_score.len()); + + for label_with_score in contract_based_connectors.labels_with_score { + let (connector, merchant_connector_id) = label_with_score.label + .split_once(':') + .ok_or(errors::RoutingError::InvalidContractBasedConnectorLabel(label_with_score.label.to_string())) + .attach_printable( + "unable to split connector_name and mca_id from the label obtained by the dynamic routing service", + )?; + + connectors.push(api_routing::RoutableConnectorChoice { + choice_kind: api_routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str(connector) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "RoutableConnectors".to_string(), + }) + .attach_printable("unable to convert String to RoutableConnectors")?, + merchant_connector_id: Some( + common_utils::id_type::MerchantConnectorAccountId::wrap( + merchant_connector_id.to_string(), + ) + .change_context(errors::RoutingError::GenericConversionError { + from: "String".to_string(), + to: "MerchantConnectorAccountId".to_string(), + }) + .attach_printable("unable to convert MerchantConnectorAccountId from string")?, + ), + }); + } + + logger::debug!(contract_based_routing_connectors=?connectors); + Ok(connectors) + } else { + Ok(routable_connectors) + } +} diff --git a/crates/router/src/core/routing.rs b/crates/router/src/core/routing.rs index 99bd2b00209..8d03e82a328 100644 --- a/crates/router/src/core/routing.rs +++ b/crates/router/src/core/routing.rs @@ -2,6 +2,8 @@ pub mod helpers; pub mod transformers; use std::collections::HashSet; +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +use api_models::routing::DynamicRoutingAlgoAccessor; use api_models::{ enums, mandates as mandates_api, routing, routing::{self as routing_types, RoutingRetrieveQuery}, @@ -12,7 +14,10 @@ use common_utils::ext_traits::AsyncExt; use diesel_models::routing_algorithm::RoutingAlgorithm; use error_stack::ResultExt; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -use external_services::grpc_client::dynamic_routing::success_rate_client::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::{ + contract_routing_client::ContractBasedDynamicRouting, + success_rate_client::SuccessBasedDynamicRouting, +}; use hyperswitch_domain_models::{mandates, payment_address}; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use router_env::logger; @@ -462,6 +467,16 @@ pub async fn link_routing_config( }, enabled_feature: _ }) if id == &algorithm_id + ) || matches!( + dynamic_routing_ref.contract_based_routing, + Some(routing::ContractRoutingAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp { + algorithm_id: Some(ref id), + timestamp: _ + }, + enabled_feature: _ + }) if id == &algorithm_id ), || { Err(errors::ApiErrorResponse::PreconditionFailed { @@ -470,7 +485,8 @@ pub async fn link_routing_config( }, )?; - dynamic_routing_ref.update_algorithm_id( + if routing_algorithm.name == helpers::SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM { + dynamic_routing_ref.update_algorithm_id( algorithm_id, dynamic_routing_ref .success_based_algorithm @@ -482,6 +498,34 @@ pub async fn link_routing_config( .enabled_feature, routing_types::DynamicRoutingType::SuccessRateBasedRouting, ); + } else if routing_algorithm.name == helpers::ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM + { + dynamic_routing_ref.update_algorithm_id( + algorithm_id, + dynamic_routing_ref + .elimination_routing_algorithm + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "missing elimination_routing_algorithm in dynamic_algorithm_ref from business_profile table", + )? + .enabled_feature, + routing_types::DynamicRoutingType::EliminationRouting, + ); + } else if routing_algorithm.name == helpers::CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM { + dynamic_routing_ref.update_algorithm_id( + algorithm_id, + dynamic_routing_ref + .contract_based_routing + .clone() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "missing contract_based_routing in dynamic_algorithm_ref from business_profile table", + )? + .enabled_feature, + routing_types::DynamicRoutingType::ContractBasedRouting, + ); + } helpers::update_business_profile_active_dynamic_algorithm_ref( db, @@ -1419,6 +1463,304 @@ pub async fn success_based_routing_update_configs( Ok(service_api::ApplicationResponse::Json(new_record)) } +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn contract_based_dynamic_routing_setup( + state: SessionState, + key_store: domain::MerchantKeyStore, + merchant_account: domain::MerchantAccount, + profile_id: common_utils::id_type::ProfileId, + feature_to_enable: routing_types::DynamicRoutingFeatures, + config: Option, +) -> RouterResult> { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + let business_profile: domain::Profile = core_utils::validate_and_get_business_profile( + db, + key_manager_state, + &key_store, + Some(&profile_id), + merchant_account.get_id(), + ) + .await? + .get_required_value("Profile") + .change_context(errors::ApiErrorResponse::ProfileNotFound { + id: profile_id.get_string_repr().to_owned(), + })?; + + let mut dynamic_routing_algo_ref: Option = + business_profile + .dynamic_routing_algorithm + .clone() + .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to deserialize dynamic routing algorithm ref from business profile", + ) + .ok() + .flatten(); + + utils::when( + dynamic_routing_algo_ref + .as_mut() + .and_then(|algo| { + algo.contract_based_routing.as_mut().map(|contract_algo| { + *contract_algo.get_enabled_features() == feature_to_enable + && contract_algo + .clone() + .get_algorithm_id_with_timestamp() + .algorithm_id + .is_some() + }) + }) + .unwrap_or(false), + || { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Contract Routing with specified features is already enabled".to_string(), + }) + }, + )?; + + if feature_to_enable == routing::DynamicRoutingFeatures::None { + let algorithm = dynamic_routing_algo_ref + .clone() + .get_required_value("dynamic_routing_algo_ref") + .attach_printable("Failed to get dynamic_routing_algo_ref")?; + return helpers::disable_dynamic_routing_algorithm( + &state, + key_store, + business_profile, + algorithm, + routing_types::DynamicRoutingType::ContractBasedRouting, + ) + .await; + } + + let config = config + .get_required_value("ContractBasedRoutingConfig") + .attach_printable("Failed to get ContractBasedRoutingConfig from request")?; + + let merchant_id = business_profile.merchant_id.clone(); + let algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + + let algo = RoutingAlgorithm { + algorithm_id: algorithm_id.clone(), + profile_id: profile_id.clone(), + merchant_id, + name: helpers::CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM.to_string(), + description: None, + kind: diesel_models::enums::RoutingAlgorithmKind::Dynamic, + algorithm_data: serde_json::json!(config), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: common_enums::TransactionType::Payment, + }; + + // 1. if dynamic_routing_algo_ref already present, insert contract based algo and disable success based + // 2. if dynamic_routing_algo_ref is not present, create a new dynamic_routing_algo_ref with contract algo set up + let final_algorithm = if let Some(mut algo) = dynamic_routing_algo_ref { + algo.update_algorithm_id( + algorithm_id, + feature_to_enable, + routing_types::DynamicRoutingType::ContractBasedRouting, + ); + if feature_to_enable == routing::DynamicRoutingFeatures::DynamicConnectorSelection { + algo.disable_algorithm_id(routing_types::DynamicRoutingType::SuccessRateBasedRouting); + } + algo + } else { + let contract_algo = routing_types::ContractRoutingAlgorithm { + algorithm_id_with_timestamp: routing_types::DynamicAlgorithmWithTimestamp::new(Some( + algorithm_id.clone(), + )), + enabled_feature: feature_to_enable, + }; + routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: None, + elimination_routing_algorithm: None, + dynamic_routing_volume_split: None, + contract_based_routing: Some(contract_algo), + } + }; + + // validate the contained mca_ids + if let Some(info_vec) = &config.label_info { + let validation_futures: Vec<_> = info_vec + .iter() + .map(|info| async { + let mca_id = info.mca_id.clone(); + let label = info.label.clone(); + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_account.get_id(), + &mca_id, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: mca_id.get_string_repr().to_owned(), + })?; + + utils::when(mca.connector_name != label, || { + Err(error_stack::Report::new( + errors::ApiErrorResponse::InvalidRequestData { + message: "Incorrect mca configuration received".to_string(), + }, + )) + })?; + + Ok::<_, error_stack::Report>(()) + }) + .collect(); + + futures::future::try_join_all(validation_futures).await?; + } + + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to insert record in routing algorithm table")?; + + helpers::update_business_profile_active_dynamic_algorithm_ref( + db, + key_manager_state, + &key_store, + business_profile, + final_algorithm, + ) + .await?; + + let new_record = record.foreign_into(); + + metrics::ROUTING_CREATE_SUCCESS_RESPONSE.add( + 1, + router_env::metric_attributes!(("profile_id", profile_id.get_string_repr().to_string())), + ); + Ok(service_api::ApplicationResponse::Json(new_record)) +} + +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +pub async fn contract_based_routing_update_configs( + state: SessionState, + request: routing_types::ContractBasedRoutingConfig, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + algorithm_id: common_utils::id_type::RoutingId, + profile_id: common_utils::id_type::ProfileId, +) -> RouterResponse { + metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE.add( + 1, + router_env::metric_attributes!(("profile_id", profile_id.get_string_repr().to_owned())), + ); + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + let dynamic_routing_algo_to_update = db + .find_routing_algorithm_by_profile_id_algorithm_id(&profile_id, &algorithm_id) + .await + .to_not_found_response(errors::ApiErrorResponse::ResourceIdNotFound)?; + + let mut config_to_update: routing::ContractBasedRoutingConfig = dynamic_routing_algo_to_update + .algorithm_data + .parse_value::("ContractBasedRoutingConfig") + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to deserialize algorithm data from routing table into ContractBasedRoutingConfig")?; + + // validate the contained mca_ids + if let Some(info_vec) = &request.label_info { + for info in info_vec { + let mca = db + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_account.get_id(), + &info.mca_id, + &key_store, + ) + .await + .change_context(errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: info.mca_id.get_string_repr().to_owned(), + })?; + + utils::when(mca.connector_name != info.label, || { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Incorrect mca configuration received".to_string(), + }) + })?; + } + } + + config_to_update.update(request); + + let updated_algorithm_id = common_utils::generate_routing_id_of_default_length(); + let timestamp = common_utils::date_time::now(); + let algo = RoutingAlgorithm { + algorithm_id: updated_algorithm_id, + profile_id: dynamic_routing_algo_to_update.profile_id, + merchant_id: dynamic_routing_algo_to_update.merchant_id, + name: dynamic_routing_algo_to_update.name, + description: dynamic_routing_algo_to_update.description, + kind: dynamic_routing_algo_to_update.kind, + algorithm_data: serde_json::json!(config_to_update), + created_at: timestamp, + modified_at: timestamp, + algorithm_for: dynamic_routing_algo_to_update.algorithm_for, + }; + let record = db + .insert_routing_algorithm(algo) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to insert record in routing algorithm table")?; + + // redact cache for contract based routing configs + let cache_key = format!( + "{}_{}", + profile_id.get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = vec![cache::CacheKind::ContractBasedDynamicRoutingCache( + cache_key.into(), + )]; + let _ = cache::redact_from_redis_and_publish( + state.store.get_cache_store().as_ref(), + cache_entries_to_redact, + ) + .await + .map_err(|e| logger::error!("unable to publish into the redact channel for evicting the contract based routing config cache {e:?}")); + + let new_record = record.foreign_into(); + + metrics::ROUTING_UPDATE_CONFIG_FOR_PROFILE_SUCCESS_RESPONSE.add( + 1, + router_env::metric_attributes!(("profile_id", profile_id.get_string_repr().to_owned())), + ); + + state + .grpc_client + .clone() + .dynamic_routing + .contract_based_client + .clone() + .async_map(|ct_client| async move { + ct_client + .invalidate_contracts( + profile_id.get_string_repr().into(), + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "Failed to invalidate the contract based routing keys".to_string(), + }) + }) + .await + .transpose()?; + + Ok(service_api::ApplicationResponse::Json(new_record)) +} + #[async_trait] pub trait GetRoutableConnectorsForChoice { async fn get_routable_connectors( diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 84de32483a8..9b6396013f4 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -2,6 +2,7 @@ //! //! Functions that are used to perform the retrieval of merchant's //! routing dict, configs, defaults +use std::fmt::Debug; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use std::str::FromStr; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] @@ -18,7 +19,10 @@ use diesel_models::dynamic_routing_stats::DynamicRoutingStatsNew; use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] -use external_services::grpc_client::dynamic_routing::success_rate_client::SuccessBasedDynamicRouting; +use external_services::grpc_client::dynamic_routing::{ + contract_routing_client::ContractBasedDynamicRouting, + success_rate_client::SuccessBasedDynamicRouting, +}; #[cfg(feature = "v1")] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -26,7 +30,7 @@ use router_env::logger; #[cfg(any(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, tracing}; use rustc_hash::FxHashSet; -use storage_impl::redis::cache; +use storage_impl::redis::cache::{self, Cacheable}; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use crate::db::errors::StorageErrorExt; @@ -45,6 +49,8 @@ pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = "Success rate based dynamic routing algorithm"; pub const ELIMINATION_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = "Elimination based dynamic routing algorithm"; +pub const CONTRACT_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = + "Contract based dynamic routing algorithm"; /// Provides us with all the configured configs of the Merchant in the ascending time configured /// manner and chooses the first of them @@ -562,78 +568,146 @@ pub fn get_default_config_key( } } -/// Retrieves cached success_based routing configs specific to tenant and profile -#[cfg(all(feature = "v1", feature = "dynamic_routing"))] -pub async fn get_cached_success_based_routing_config_for_profile( - state: &SessionState, - key: &str, -) -> Option> { - cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE - .get_val::>(cache::CacheKey { - key: key.to_string(), - prefix: state.tenant.redis_key_prefix.clone(), - }) +#[async_trait::async_trait] +pub trait DynamicRoutingCache { + async fn get_cached_dynamic_routing_config_for_profile( + state: &SessionState, + key: &str, + ) -> Option>; + + async fn refresh_dynamic_routing_cache( + state: &SessionState, + key: &str, + func: F, + ) -> RouterResult + where + F: FnOnce() -> Fut + Send, + T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone, + Fut: futures::Future> + Send; +} + +#[async_trait::async_trait] +impl DynamicRoutingCache for routing_types::SuccessBasedRoutingConfig { + async fn get_cached_dynamic_routing_config_for_profile( + state: &SessionState, + key: &str, + ) -> Option> { + cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE + .get_val::>(cache::CacheKey { + key: key.to_string(), + prefix: state.tenant.redis_key_prefix.clone(), + }) + .await + } + + async fn refresh_dynamic_routing_cache( + state: &SessionState, + key: &str, + func: F, + ) -> RouterResult + where + F: FnOnce() -> Fut + Send, + T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone, + Fut: futures::Future> + Send, + { + cache::get_or_populate_in_memory( + state.store.get_cache_store().as_ref(), + key, + func, + &cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, + ) .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to populate SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE") + } } -/// Refreshes the cached success_based routing configs specific to tenant and profile -#[cfg(feature = "v1")] -pub async fn refresh_success_based_routing_cache( - state: &SessionState, - key: &str, - success_based_routing_config: routing_types::SuccessBasedRoutingConfig, -) -> Arc { - let config = Arc::new(success_based_routing_config); - cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE - .push( - cache::CacheKey { +#[async_trait::async_trait] +impl DynamicRoutingCache for routing_types::ContractBasedRoutingConfig { + async fn get_cached_dynamic_routing_config_for_profile( + state: &SessionState, + key: &str, + ) -> Option> { + cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE + .get_val::>(cache::CacheKey { key: key.to_string(), prefix: state.tenant.redis_key_prefix.clone(), - }, - config.clone(), + }) + .await + } + + async fn refresh_dynamic_routing_cache( + state: &SessionState, + key: &str, + func: F, + ) -> RouterResult + where + F: FnOnce() -> Fut + Send, + T: Cacheable + serde::Serialize + serde::de::DeserializeOwned + Debug + Clone, + Fut: futures::Future> + Send, + { + cache::get_or_populate_in_memory( + state.store.get_cache_store().as_ref(), + key, + func, + &cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE, ) - .await; - config + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to populate CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE") + } } -/// Checked fetch of success based routing configs +/// Cfetch dynamic routing configs #[cfg(all(feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] -pub async fn fetch_success_based_routing_configs( +pub async fn fetch_dynamic_routing_configs( state: &SessionState, - business_profile: &domain::Profile, - success_based_routing_id: id_type::RoutingId, -) -> RouterResult { + profile_id: &id_type::ProfileId, + routing_id: id_type::RoutingId, +) -> RouterResult +where + T: serde::de::DeserializeOwned + + Clone + + DynamicRoutingCache + + Cacheable + + serde::Serialize + + Debug, +{ let key = format!( "{}_{}", - business_profile.get_id().get_string_repr(), - success_based_routing_id.get_string_repr() + profile_id.get_string_repr(), + routing_id.get_string_repr() ); if let Some(config) = - get_cached_success_based_routing_config_for_profile(state, key.as_str()).await + T::get_cached_dynamic_routing_config_for_profile(state, key.as_str()).await { Ok(config.as_ref().clone()) } else { - let success_rate_algorithm = state - .store - .find_routing_algorithm_by_profile_id_algorithm_id( - business_profile.get_id(), - &success_based_routing_id, - ) - .await - .change_context(errors::ApiErrorResponse::ResourceIdNotFound) - .attach_printable("unable to retrieve success_rate_algorithm for profile from db")?; - - let success_rate_config = success_rate_algorithm - .algorithm_data - .parse_value::("SuccessBasedRoutingConfig") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to parse success_based_routing_config struct")?; + let func = || async { + let routing_algorithm = state + .store + .find_routing_algorithm_by_profile_id_algorithm_id(profile_id, &routing_id) + .await + .change_context(errors::StorageError::ValueNotFound( + "RoutingAlgorithm".to_string(), + )) + .attach_printable("unable to retrieve routing_algorithm for profile from db")?; + + let dynamic_routing_config = routing_algorithm + .algorithm_data + .parse_value::("dynamic_routing_config") + .change_context(errors::StorageError::DeserializationFailed) + .attach_printable("unable to parse dynamic_routing_config")?; + + Ok(dynamic_routing_config) + }; - refresh_success_based_routing_cache(state, key.as_str(), success_rate_config.clone()).await; + let dynamic_routing_config = + T::refresh_dynamic_routing_cache(state, key.as_str(), func).await?; - Ok(success_rate_config) + Ok(dynamic_routing_config) } } @@ -644,20 +718,11 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( state: &SessionState, payment_attempt: &storage::PaymentAttempt, routable_connectors: Vec, - business_profile: &domain::Profile, - success_based_routing_config_params_interpolator: SuccessBasedRoutingConfigParamsInterpolator, + profile_id: &id_type::ProfileId, + dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { - let success_based_dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef = - business_profile - .dynamic_routing_algorithm - .clone() - .map(|val| val.parse_value("DynamicRoutingAlgorithmRef")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to deserialize DynamicRoutingAlgorithmRef from JSON")? - .unwrap_or_default(); - - let success_based_algo_ref = success_based_dynamic_routing_algo_ref + let success_based_algo_ref = dynamic_routing_algo_ref .success_based_algorithm .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; @@ -678,22 +743,23 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( }, )?; - let success_based_routing_configs = fetch_success_based_routing_configs( - state, - business_profile, - success_based_algo_ref - .algorithm_id_with_timestamp - .algorithm_id - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "success_based_routing_algorithm_id not found in business_profile", - )?, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; + let success_based_routing_configs = + fetch_dynamic_routing_configs::( + state, + profile_id, + success_based_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "success_based_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; - let success_based_routing_config_params = success_based_routing_config_params_interpolator + let success_based_routing_config_params = dynamic_routing_config_params_interpolator .get_string_val( success_based_routing_configs .params @@ -704,7 +770,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( let success_based_connectors = client .calculate_entity_and_global_success_rate( - business_profile.get_id().get_string_repr().into(), + profile_id.get_string_repr().into(), success_based_routing_configs.clone(), success_based_routing_config_params.clone(), routable_connectors.clone(), @@ -717,7 +783,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( )?; let payment_status_attribute = - get_desired_payment_status_for_success_routing_metrics(payment_attempt.status); + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); let first_merchant_success_based_connector = &success_based_connectors .entity_scores_with_labels @@ -743,7 +809,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", )?; - let outcome = get_success_based_metrics_outcome_for_payment( + let outcome = get_dynamic_routing_based_metrics_outcome_for_payment( payment_status_attribute, payment_connector.to_string(), first_merchant_success_based_connector_label.to_string(), @@ -852,7 +918,7 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( client .update_success_rate( - business_profile.get_id().get_string_repr().into(), + profile_id.get_string_repr().into(), success_based_routing_configs, success_based_routing_config_params, vec![routing_types::RoutableConnectorChoiceWithStatus::new( @@ -880,8 +946,212 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( } } +/// metrics for contract based dynamic routing +#[cfg(all(feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn push_metrics_with_update_window_for_contract_based_routing( + state: &SessionState, + payment_attempt: &storage::PaymentAttempt, + routable_connectors: Vec, + profile_id: &id_type::ProfileId, + dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, + _dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, +) -> RouterResult<()> { + let contract_routing_algo_ref = dynamic_routing_algo_ref + .contract_based_routing + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("contract_routing_algorithm not found in dynamic_routing_algorithm from business_profile table")?; + + if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { + let client = state + .grpc_client + .dynamic_routing + .contract_based_client + .clone() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract_routing gRPC client not found".to_string(), + })?; + + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; + + let contract_based_routing_config = + fetch_dynamic_routing_configs::( + state, + profile_id, + contract_routing_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "contract_based_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to retrieve contract based dynamic routing configs")?; + + let mut existing_label_info = None; + + contract_based_routing_config + .label_info + .as_ref() + .map(|label_info_vec| { + for label_info in label_info_vec { + if Some(&label_info.mca_id) == payment_attempt.merchant_connector_id.as_ref() { + existing_label_info = Some(label_info.clone()); + } + } + }); + + let final_label_info = existing_label_info + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("unable to get LabelInformation from ContractBasedRoutingConfig")?; + + logger::debug!( + "contract based routing: matched LabelInformation - {:?}", + final_label_info + ); + + let request_label_info = routing_types::LabelInformation { + label: format!( + "{}:{}", + final_label_info.label.clone(), + final_label_info.mca_id.get_string_repr() + ), + target_count: final_label_info.target_count, + target_time: final_label_info.target_time, + mca_id: final_label_info.mca_id.to_owned(), + }; + + let payment_status_attribute = + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + + if payment_status_attribute == common_enums::AttemptStatus::Charged { + client + .update_contracts( + profile_id.get_string_repr().into(), + vec![request_label_info], + "".to_string(), + vec![], + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update contract based routing window in dynamic routing service", + )?; + } + + let contract_scores = client + .calculate_contract_score( + profile_id.get_string_repr().into(), + contract_based_routing_config.clone(), + "".to_string(), + routable_connectors.clone(), + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to calculate/fetch contract scores from dynamic routing service", + )?; + + let first_contract_based_connector = &contract_scores + .labels_with_score + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first connector from list of connectors obtained from dynamic routing service", + )?; + + let (first_contract_based_connector, connector_score, current_payment_cnt) = (first_contract_based_connector.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", + first_contract_based_connector + ))? + .0, first_contract_based_connector.score, first_contract_based_connector.current_count ); + + core_metrics::DYNAMIC_CONTRACT_BASED_ROUTING.add( + 1, + router_env::metric_attributes!( + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), + ), + ( + "merchant_profile_id", + format!( + "{}:{}", + payment_attempt.merchant_id.get_string_repr(), + payment_attempt.profile_id.get_string_repr() + ), + ), + ( + "contract_based_routing_connector", + first_contract_based_connector.to_string(), + ), + ( + "contract_based_routing_connector_score", + connector_score.to_string(), + ), + ( + "current_payment_count_contract_based_routing_connector", + current_payment_cnt.to_string(), + ), + ("payment_connector", payment_connector.to_string()), + ( + "currency", + payment_attempt + .currency + .map_or_else(|| "None".to_string(), |currency| currency.to_string()), + ), + ( + "payment_method", + payment_attempt.payment_method.map_or_else( + || "None".to_string(), + |payment_method| payment_method.to_string(), + ), + ), + ( + "payment_method_type", + payment_attempt.payment_method_type.map_or_else( + || "None".to_string(), + |payment_method_type| payment_method_type.to_string(), + ), + ), + ( + "capture_method", + payment_attempt.capture_method.map_or_else( + || "None".to_string(), + |capture_method| capture_method.to_string(), + ), + ), + ( + "authentication_type", + payment_attempt.authentication_type.map_or_else( + || "None".to_string(), + |authentication_type| authentication_type.to_string(), + ), + ), + ("payment_status", payment_attempt.status.to_string()), + ), + ); + logger::debug!("successfully pushed contract_based_routing metrics"); + + Ok(()) + } else { + Ok(()) + } +} + #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -fn get_desired_payment_status_for_success_routing_metrics( +fn get_desired_payment_status_for_dynamic_routing_metrics( attempt_status: common_enums::AttemptStatus, ) -> common_enums::AttemptStatus { match attempt_status { @@ -917,7 +1187,7 @@ fn get_desired_payment_status_for_success_routing_metrics( } #[cfg(all(feature = "v1", feature = "dynamic_routing"))] -fn get_success_based_metrics_outcome_for_payment( +fn get_dynamic_routing_based_metrics_outcome_for_payment( payment_status_attribute: common_enums::AttemptStatus, payment_connector: String, first_success_based_connector: String, @@ -957,7 +1227,6 @@ pub async fn disable_dynamic_routing_algorithm( ) -> RouterResult> { let db = state.store.as_ref(); let key_manager_state = &state.into(); - let timestamp = common_utils::date_time::now_unix_timestamp(); let profile_id = business_profile.get_id().clone(); let (algorithm_id, dynamic_routing_algorithm, cache_entries_to_redact) = match dynamic_routing_type { @@ -988,14 +1257,12 @@ pub async fn disable_dynamic_routing_algorithm( routing_types::DynamicRoutingAlgorithmRef { success_based_algorithm: Some(routing_types::SuccessBasedAlgorithm { algorithm_id_with_timestamp: - routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: None, - timestamp, - }, + routing_types::DynamicAlgorithmWithTimestamp::new(None), enabled_feature: routing_types::DynamicRoutingFeatures::None, }), elimination_routing_algorithm: dynamic_routing_algo_ref .elimination_routing_algorithm, + contract_based_routing: dynamic_routing_algo_ref.contract_based_routing, dynamic_routing_volume_split: dynamic_routing_algo_ref .dynamic_routing_volume_split, }, @@ -1033,13 +1300,49 @@ pub async fn disable_dynamic_routing_algorithm( elimination_routing_algorithm: Some( routing_types::EliminationRoutingAlgorithm { algorithm_id_with_timestamp: - routing_types::DynamicAlgorithmWithTimestamp { - algorithm_id: None, - timestamp, - }, + routing_types::DynamicAlgorithmWithTimestamp::new(None), enabled_feature: routing_types::DynamicRoutingFeatures::None, }, ), + contract_based_routing: dynamic_routing_algo_ref.contract_based_routing, + }, + cache_entries_to_redact, + ) + } + routing_types::DynamicRoutingType::ContractBasedRouting => { + let Some(algorithm_ref) = dynamic_routing_algo_ref.contract_based_routing else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Contract routing is already disabled".to_string(), + })? + }; + let Some(algorithm_id) = algorithm_ref.algorithm_id_with_timestamp.algorithm_id + else { + Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Algorithm is already inactive".to_string(), + })? + }; + let cache_key = format!( + "{}_{}", + business_profile.get_id().get_string_repr(), + algorithm_id.get_string_repr() + ); + let cache_entries_to_redact = + vec![cache::CacheKind::ContractBasedDynamicRoutingCache( + cache_key.into(), + )]; + ( + algorithm_id, + routing_types::DynamicRoutingAlgorithmRef { + success_based_algorithm: dynamic_routing_algo_ref.success_based_algorithm, + elimination_routing_algorithm: dynamic_routing_algo_ref + .elimination_routing_algorithm, + dynamic_routing_volume_split: dynamic_routing_algo_ref + .dynamic_routing_volume_split, + contract_based_routing: Some(routing_types::ContractRoutingAlgorithm { + algorithm_id_with_timestamp: + routing_types::DynamicAlgorithmWithTimestamp::new(None), + enabled_feature: routing_types::DynamicRoutingFeatures::None, + }), }, cache_entries_to_redact, ) @@ -1088,15 +1391,18 @@ pub async fn enable_dynamic_routing_algorithm( dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, dynamic_routing_type: routing_types::DynamicRoutingType, ) -> RouterResult> { - let dynamic_routing = dynamic_routing_algo_ref.clone(); + let mut dynamic_routing = dynamic_routing_algo_ref.clone(); match dynamic_routing_type { routing_types::DynamicRoutingType::SuccessRateBasedRouting => { + dynamic_routing + .disable_algorithm_id(routing_types::DynamicRoutingType::ContractBasedRouting); + enable_specific_routing_algorithm( state, key_store, business_profile, feature_to_enable, - dynamic_routing_algo_ref, + dynamic_routing.clone(), dynamic_routing_type, dynamic_routing.success_based_algorithm, ) @@ -1108,12 +1414,18 @@ pub async fn enable_dynamic_routing_algorithm( key_store, business_profile, feature_to_enable, - dynamic_routing_algo_ref, + dynamic_routing.clone(), dynamic_routing_type, dynamic_routing.elimination_routing_algorithm, ) .await } + routing_types::DynamicRoutingType::ContractBasedRouting => { + Err((errors::ApiErrorResponse::InvalidRequestData { + message: "Contract routing cannot be set as default".to_string(), + }) + .into()) + } } } @@ -1128,7 +1440,7 @@ pub async fn enable_specific_routing_algorithm( algo_type: Option, ) -> RouterResult> where - A: routing_types::DynamicRoutingAlgoAccessor + Clone + std::fmt::Debug, + A: routing_types::DynamicRoutingAlgoAccessor + Clone + Debug, { // Algorithm wasn't created yet let Some(mut algo_type) = algo_type else { @@ -1169,9 +1481,8 @@ where } .into()); }; - *algo_type_enabled_features = feature_to_enable.clone(); - dynamic_routing_algo_ref - .update_specific_ref(dynamic_routing_type.clone(), feature_to_enable.clone()); + *algo_type_enabled_features = feature_to_enable; + dynamic_routing_algo_ref.update_enabled_features(dynamic_routing_type, feature_to_enable); update_business_profile_active_dynamic_algorithm_ref( db, &state.into(), @@ -1242,6 +1553,13 @@ pub async fn default_specific_dynamic_routing_setup( algorithm_for: common_enums::TransactionType::Payment, } } + + routing_types::DynamicRoutingType::ContractBasedRouting => { + return Err((errors::ApiErrorResponse::InvalidRequestData { + message: "Contract routing cannot be set as default".to_string(), + }) + .into()) + } }; let record = db @@ -1273,7 +1591,8 @@ pub async fn default_specific_dynamic_routing_setup( Ok(ApplicationResponse::Json(new_record)) } -pub struct SuccessBasedRoutingConfigParamsInterpolator { +#[derive(Debug, Clone)] +pub struct DynamicRoutingConfigParamsInterpolator { pub payment_method: Option, pub payment_method_type: Option, pub authentication_type: Option, @@ -1283,7 +1602,7 @@ pub struct SuccessBasedRoutingConfigParamsInterpolator { pub card_bin: Option, } -impl SuccessBasedRoutingConfigParamsInterpolator { +impl DynamicRoutingConfigParamsInterpolator { pub fn new( payment_method: Option, payment_method_type: Option, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 7f7ea767108..c2363500c04 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1870,6 +1870,10 @@ impl Profile { }), )), ) + .service( + web::resource("/set_volume_split") + .route(web::post().to(routing::set_dynamic_routing_volume_split)), + ) .service( web::scope("/elimination").service( web::resource("/toggle") @@ -1877,8 +1881,17 @@ impl Profile { ), ) .service( - web::resource("/set_volume_split") - .route(web::post().to(routing::set_dynamic_routing_volume_split)), + web::scope("/contracts") + .service(web::resource("/toggle").route( + web::post().to(routing::contract_based_routing_setup_config), + )) + .service(web::resource("/config/{algorithm_id}").route( + web::patch().to(|state, req, path, payload| { + routing::contract_based_routing_update_configs( + state, req, path, payload, + ) + }), + )), ), ); } diff --git a/crates/router/src/routes/metrics/bg_metrics_collector.rs b/crates/router/src/routes/metrics/bg_metrics_collector.rs index f3ba7076e53..c0f4062e15d 100644 --- a/crates/router/src/routes/metrics/bg_metrics_collector.rs +++ b/crates/router/src/routes/metrics/bg_metrics_collector.rs @@ -14,6 +14,9 @@ pub fn spawn_metrics_collector(metrics_collection_interval_in_secs: Option) &cache::PM_FILTERS_CGRAPH_CACHE, &cache::DECISION_MANAGER_CACHE, &cache::SURCHARGE_CACHE, + &cache::SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, + &cache::CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE, + &cache::ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE, ]; tokio::spawn(async move { diff --git a/crates/router/src/routes/routing.rs b/crates/router/src/routes/routing.rs index e7227e1f752..ac0f997a278 100644 --- a/crates/router/src/routes/routing.rs +++ b/crates/router/src/routes/routing.rs @@ -1130,7 +1130,7 @@ pub async fn toggle_success_based_routing( pub async fn success_based_routing_update_configs( state: web::Data, req: HttpRequest, - path: web::Path, + path: web::Path, json_payload: web::Json, ) -> impl Responder { let flow = Flow::UpdateDynamicRoutingConfigs; @@ -1165,6 +1165,100 @@ pub async fn success_based_routing_update_configs( )) .await } + +#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn contract_based_routing_setup_config( + state: web::Data, + req: HttpRequest, + path: web::Path, + query: web::Query, + json_payload: Option>, +) -> impl Responder { + let flow = Flow::ToggleDynamicRouting; + let routing_payload_wrapper = routing_types::ContractBasedRoutingSetupPayloadWrapper { + config: json_payload.map(|json| json.into_inner()), + profile_id: path.into_inner().profile_id, + features_to_enable: query.into_inner().enable, + }; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + routing_payload_wrapper.clone(), + |state, + auth: auth::AuthenticationData, + wrapper: routing_types::ContractBasedRoutingSetupPayloadWrapper, + _| async move { + Box::pin(routing::contract_based_dynamic_routing_setup( + state, + auth.key_store, + auth.merchant_account, + wrapper.profile_id, + wrapper.features_to_enable, + wrapper.config, + )) + .await + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuthProfileFromRoute { + profile_id: routing_payload_wrapper.profile_id, + required_permission: Permission::ProfileRoutingWrite, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] +#[instrument(skip_all)] +pub async fn contract_based_routing_update_configs( + state: web::Data, + req: HttpRequest, + path: web::Path, + json_payload: web::Json, +) -> impl Responder { + let flow = Flow::UpdateDynamicRoutingConfigs; + let routing_payload_wrapper = routing_types::ContractBasedRoutingPayloadWrapper { + updated_config: json_payload.into_inner(), + algorithm_id: path.algorithm_id.clone(), + profile_id: path.profile_id.clone(), + }; + Box::pin(oss_api::server_wrap( + flow, + state, + &req, + routing_payload_wrapper.clone(), + |state, + auth: auth::AuthenticationData, + wrapper: routing_types::ContractBasedRoutingPayloadWrapper, + _| async { + Box::pin(routing::contract_based_routing_update_configs( + state, + wrapper.updated_config, + auth.merchant_account, + auth.key_store, + wrapper.algorithm_id, + wrapper.profile_id, + )) + .await + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuthProfileFromRoute { + profile_id: routing_payload_wrapper.profile_id, + required_permission: Permission::ProfileRoutingWrite, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(all(feature = "olap", feature = "v1", feature = "dynamic_routing"))] #[instrument(skip_all)] pub async fn toggle_elimination_routing( diff --git a/crates/storage_impl/src/redis/cache.rs b/crates/storage_impl/src/redis/cache.rs index 8302b5bf933..fb752e64b09 100644 --- a/crates/storage_impl/src/redis/cache.rs +++ b/crates/storage_impl/src/redis/cache.rs @@ -92,6 +92,16 @@ pub static ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| ) }); +/// Contract Routing based Dynamic Algorithm Cache +pub static CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE: Lazy = Lazy::new(|| { + Cache::new( + "CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE", + CACHE_TTL, + CACHE_TTI, + Some(MAX_CAPACITY), + ) +}); + /// Trait which defines the behaviour of types that's gonna be stored in Cache pub trait Cacheable: Any + Send + Sync + DynClone { fn as_any(&self) -> &dyn Any; @@ -113,6 +123,7 @@ pub enum CacheKind<'a> { CGraph(Cow<'a, str>), SuccessBasedDynamicRoutingCache(Cow<'a, str>), EliminationBasedDynamicRoutingCache(Cow<'a, str>), + ContractBasedDynamicRoutingCache(Cow<'a, str>), PmFiltersCGraph(Cow<'a, str>), All(Cow<'a, str>), } @@ -128,6 +139,7 @@ impl CacheKind<'_> { | CacheKind::CGraph(key) | CacheKind::SuccessBasedDynamicRoutingCache(key) | CacheKind::EliminationBasedDynamicRoutingCache(key) + | CacheKind::ContractBasedDynamicRoutingCache(key) | CacheKind::PmFiltersCGraph(key) | CacheKind::All(key) => key, } diff --git a/crates/storage_impl/src/redis/pub_sub.rs b/crates/storage_impl/src/redis/pub_sub.rs index 373ac370e2f..28b76148765 100644 --- a/crates/storage_impl/src/redis/pub_sub.rs +++ b/crates/storage_impl/src/redis/pub_sub.rs @@ -6,8 +6,9 @@ use router_env::{logger, tracing::Instrument}; use crate::redis::cache::{ CacheKey, CacheKind, CacheRedact, ACCOUNTS_CACHE, CGRAPH_CACHE, CONFIG_CACHE, - DECISION_MANAGER_CACHE, ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE, PM_FILTERS_CGRAPH_CACHE, - ROUTING_CACHE, SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, + CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE, DECISION_MANAGER_CACHE, + ELIMINATION_BASED_DYNAMIC_ALGORITHM_CACHE, PM_FILTERS_CGRAPH_CACHE, ROUTING_CACHE, + SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE, SURCHARGE_CACHE, }; #[async_trait::async_trait] @@ -147,6 +148,15 @@ impl PubSubInterface for std::sync::Arc { .await; key } + CacheKind::ContractBasedDynamicRoutingCache(key) => { + CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; + key + } CacheKind::SuccessBasedDynamicRoutingCache(key) => { SUCCESS_BASED_DYNAMIC_ALGORITHM_CACHE .remove(CacheKey { @@ -220,6 +230,12 @@ impl PubSubInterface for std::sync::Arc { prefix: message.tenant.clone(), }) .await; + CONTRACT_BASED_DYNAMIC_ALGORITHM_CACHE + .remove(CacheKey { + key: key.to_string(), + prefix: message.tenant.clone(), + }) + .await; ROUTING_CACHE .remove(CacheKey { key: key.to_string(), diff --git a/proto/contract_routing.proto b/proto/contract_routing.proto new file mode 100644 index 00000000000..6b842e3096e --- /dev/null +++ b/proto/contract_routing.proto @@ -0,0 +1,76 @@ +syntax = "proto3"; +package contract_routing; + +service ContractScoreCalculator { + rpc FetchContractScore (CalContractScoreRequest) returns (CalContractScoreResponse); + + rpc UpdateContract (UpdateContractRequest) returns (UpdateContractResponse); + + rpc InvalidateContract (InvalidateContractRequest) returns (InvalidateContractResponse); +} + +// API-1 types +message CalContractScoreRequest { + string id = 1; + string params = 2; + repeated string labels = 3; + CalContractScoreConfig config = 4; +} + +message CalContractScoreConfig { + repeated double constants = 1; + TimeScale time_scale = 2; +} + +message TimeScale { + enum Scale { + Day = 0; + Month = 1; + } + Scale time_scale = 1; +} + +message CalContractScoreResponse { + repeated ScoreData labels_with_score = 1; +} + +message ScoreData { + double score = 1; + string label = 2; + uint64 current_count = 3; +} + +// API-2 types +message UpdateContractRequest { + string id = 1; + string params = 2; + repeated LabelInformation labels_information = 3; +} + +message LabelInformation { + string label = 1; + uint64 target_count = 2; + uint64 target_time = 3; + uint64 current_count = 4; +} + +message UpdateContractResponse { + enum UpdationStatus { + CONTRACT_UPDATION_SUCCEEDED = 0; + CONTRACT_UPDATION_FAILED = 1; + } + UpdationStatus status = 1; +} + +// API-3 types +message InvalidateContractRequest { + string id = 1; +} + +message InvalidateContractResponse { + enum InvalidationStatus { + CONTRACT_INVALIDATION_SUCCEEDED = 0; + CONTRACT_INVALIDATION_FAILED = 1; + } + InvalidationStatus status = 1; +} \ No newline at end of file From 8ae5267b91cfb37b14df1acf5fd7dfc2570b58ce Mon Sep 17 00:00:00 2001 From: Anurag Date: Thu, 6 Feb 2025 19:16:11 +0530 Subject: [PATCH 056/133] fix(connector): handle unexpected error response from bluesnap connector (#7120) Co-authored-by: Anurag Singh Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> --- .../src/connectors/bluesnap.rs | 148 ++++++++++-------- 1 file changed, 79 insertions(+), 69 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/bluesnap.rs b/crates/hyperswitch_connectors/src/connectors/bluesnap.rs index 8fac36424e2..3f319ee74f5 100644 --- a/crates/hyperswitch_connectors/src/connectors/bluesnap.rs +++ b/crates/hyperswitch_connectors/src/connectors/bluesnap.rs @@ -11,7 +11,7 @@ use common_utils::{ request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; -use error_stack::{report, ResultExt}; +use error_stack::{report, Report, ResultExt}; use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ @@ -55,9 +55,9 @@ use crate::{ utils::{ construct_not_supported_error_report, convert_amount, get_error_code_error_message_based_on_priority, get_header_key_value, get_http_header, - to_connector_meta_from_secret, to_currency_lower_unit, ConnectorErrorType, - ConnectorErrorTypeMapping, ForeignTryFrom, PaymentsAuthorizeRequestData, - RefundsRequestData, RouterData as _, + handle_json_response_deserialization_failure, to_connector_meta_from_secret, + to_currency_lower_unit, ConnectorErrorType, ConnectorErrorTypeMapping, ForeignTryFrom, + PaymentsAuthorizeRequestData, RefundsRequestData, RouterData as _, }, }; @@ -132,74 +132,84 @@ impl ConnectorCommon for Bluesnap { event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { logger::debug!(bluesnap_error_response=?res); - let response: bluesnap::BluesnapErrors = res - .response - .parse_struct("BluesnapErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_error_response_body(&response)); - router_env::logger::info!(connector_response=?response); + let response_data: Result< + bluesnap::BluesnapErrors, + Report, + > = res.response.parse_struct("BluesnapErrors"); + + match response_data { + Ok(response) => { + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); - let response_error_message = match response { - bluesnap::BluesnapErrors::Payment(error_response) => { - let error_list = error_response.message.clone(); - let option_error_code_message = get_error_code_error_message_based_on_priority( - self.clone(), - error_list.into_iter().map(|errors| errors.into()).collect(), - ); - let reason = error_response - .message - .iter() - .map(|error| error.description.clone()) - .collect::>() - .join(" & "); - ErrorResponse { - status_code: res.status_code, - code: option_error_code_message - .clone() - .map(|error_code_message| error_code_message.error_code) - .unwrap_or(NO_ERROR_CODE.to_string()), - message: option_error_code_message - .map(|error_code_message| error_code_message.error_message) - .unwrap_or(NO_ERROR_MESSAGE.to_string()), - reason: Some(reason), - attempt_status: None, - connector_transaction_id: None, - } - } - bluesnap::BluesnapErrors::Auth(error_res) => ErrorResponse { - status_code: res.status_code, - code: error_res.error_code.clone(), - message: error_res.error_name.clone().unwrap_or(error_res.error_code), - reason: Some(error_res.error_description), - attempt_status: None, - connector_transaction_id: None, - }, - bluesnap::BluesnapErrors::General(error_response) => { - let (error_res, attempt_status) = if res.status_code == 403 - && error_response.contains(BLUESNAP_TRANSACTION_NOT_FOUND) - { - ( - format!( - "{} in bluesnap dashboard", - REQUEST_TIMEOUT_PAYMENT_NOT_FOUND - ), - Some(enums::AttemptStatus::Failure), // when bluesnap throws 403 for payment not found, we update the payment status to failure. - ) - } else { - (error_response.clone(), None) + let response_error_message = match response { + bluesnap::BluesnapErrors::Payment(error_response) => { + let error_list = error_response.message.clone(); + let option_error_code_message = + get_error_code_error_message_based_on_priority( + self.clone(), + error_list.into_iter().map(|errors| errors.into()).collect(), + ); + let reason = error_response + .message + .iter() + .map(|error| error.description.clone()) + .collect::>() + .join(" & "); + ErrorResponse { + status_code: res.status_code, + code: option_error_code_message + .clone() + .map(|error_code_message| error_code_message.error_code) + .unwrap_or(NO_ERROR_CODE.to_string()), + message: option_error_code_message + .map(|error_code_message| error_code_message.error_message) + .unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: Some(reason), + attempt_status: None, + connector_transaction_id: None, + } + } + bluesnap::BluesnapErrors::Auth(error_res) => ErrorResponse { + status_code: res.status_code, + code: error_res.error_code.clone(), + message: error_res.error_name.clone().unwrap_or(error_res.error_code), + reason: Some(error_res.error_description), + attempt_status: None, + connector_transaction_id: None, + }, + bluesnap::BluesnapErrors::General(error_response) => { + let (error_res, attempt_status) = if res.status_code == 403 + && error_response.contains(BLUESNAP_TRANSACTION_NOT_FOUND) + { + ( + format!( + "{} in bluesnap dashboard", + REQUEST_TIMEOUT_PAYMENT_NOT_FOUND + ), + Some(enums::AttemptStatus::Failure), // when bluesnap throws 403 for payment not found, we update the payment status to failure. + ) + } else { + (error_response.clone(), None) + }; + ErrorResponse { + status_code: res.status_code, + code: NO_ERROR_CODE.to_string(), + message: error_response, + reason: Some(error_res), + attempt_status, + connector_transaction_id: None, + } + } }; - ErrorResponse { - status_code: res.status_code, - code: NO_ERROR_CODE.to_string(), - message: error_response, - reason: Some(error_res), - attempt_status, - connector_transaction_id: None, - } + Ok(response_error_message) + } + Err(error_msg) => { + event_builder.map(|event| event.set_error(serde_json::json!({"error": res.response.escape_ascii().to_string(), "status_code": res.status_code}))); + router_env::logger::error!(deserialization_error =? error_msg); + handle_json_response_deserialization_failure(res, "bluesnap") } - }; - Ok(response_error_message) + } } } From 97e9270ed4458a24207ea5434d65c54fb4b6237d Mon Sep 17 00:00:00 2001 From: Amey Wale <76102448+AmeyWale@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:16:20 +0530 Subject: [PATCH 057/133] refactor(customer): return redacted customer instead of error (#7122) --- crates/router/src/core/customers.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index 4f69cf41963..dabc9d37086 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -463,7 +463,7 @@ pub async fn retrieve_customer( let key_manager_state = &(&state).into(); let response = db - .find_customer_by_customer_id_merchant_id( + .find_customer_optional_with_redacted_customer_details_by_customer_id_merchant_id( key_manager_state, &customer_id, merchant_account.get_id(), @@ -471,7 +471,9 @@ pub async fn retrieve_customer( merchant_account.storage_scheme, ) .await - .switch()?; + .switch()? + .ok_or(errors::CustomersErrorResponse::CustomerNotFound)?; + let address = match &response.address_id { Some(address_id) => Some(api_models::payments::AddressDetails::from( db.find_address_by_address_id(key_manager_state, address_id, &key_store) From e17ffd1257adc1618ed60dee81ea1e7df84cb3d5 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:16:36 +0530 Subject: [PATCH 058/133] feat(core): Add support for v2 payments get intent using merchant reference id (#7123) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../diesel_models/src/query/payment_intent.rs | 17 +++++ .../src/payments/payment_intent.rs | 9 +++ crates/router/src/core/payments.rs | 64 +++++++++++++++++++ crates/router/src/db/kafka_store.rs | 23 +++++++ crates/router/src/routes/app.rs | 6 ++ crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/payments.rs | 41 ++++++++++++ crates/router_env/src/logger/types.rs | 2 + .../src/mock_db/payment_intent.rs | 22 +++++++ .../src/payments/payment_intent.rs | 59 +++++++++++++++++ 10 files changed, 245 insertions(+), 1 deletion(-) diff --git a/crates/diesel_models/src/query/payment_intent.rs b/crates/diesel_models/src/query/payment_intent.rs index 4f4099eca01..3778de3688e 100644 --- a/crates/diesel_models/src/query/payment_intent.rs +++ b/crates/diesel_models/src/query/payment_intent.rs @@ -87,6 +87,23 @@ impl PaymentIntent { .await } + // This query should be removed in the future because direct queries to the intent table without an intent ID are not allowed. + // In an active-active setup, a lookup table should be implemented, and the merchant reference ID will serve as the idempotency key. + #[cfg(feature = "v2")] + pub async fn find_by_merchant_reference_id_profile_id( + conn: &PgPooledConn, + merchant_reference_id: &common_utils::id_type::PaymentReferenceId, + profile_id: &common_utils::id_type::ProfileId, + ) -> StorageResult { + generics::generic_find_one::<::Table, _, _>( + conn, + dsl::profile_id + .eq(profile_id.to_owned()) + .and(dsl::merchant_reference_id.eq(merchant_reference_id.to_owned())), + ) + .await + } + #[cfg(feature = "v1")] pub async fn find_by_payment_id_merchant_id( conn: &PgPooledConn, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 0afefbab9a4..2406e5f4b1b 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -63,6 +63,15 @@ pub trait PaymentIntentInterface { merchant_key_store: &MerchantKeyStore, storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result; + #[cfg(feature = "v2")] + async fn find_payment_intent_by_merchant_reference_id_profile_id( + &self, + state: &KeyManagerState, + merchant_reference_id: &id_type::PaymentReferenceId, + profile_id: &id_type::ProfileId, + merchant_key_store: &MerchantKeyStore, + storage_scheme: &common_enums::MerchantStorageScheme, + ) -> error_stack::Result; #[cfg(feature = "v2")] async fn find_payment_intent_by_id( diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index f86072fdcc2..592dda60177 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1525,6 +1525,70 @@ where ) } +#[cfg(feature = "v2")] +#[allow(clippy::too_many_arguments)] +pub async fn payments_get_intent_using_merchant_reference( + state: SessionState, + merchant_account: domain::MerchantAccount, + profile: domain::Profile, + key_store: domain::MerchantKeyStore, + req_state: ReqState, + merchant_reference_id: &id_type::PaymentReferenceId, + header_payload: HeaderPayload, + platform_merchant_account: Option, +) -> RouterResponse { + let db = state.store.as_ref(); + let storage_scheme = merchant_account.storage_scheme; + let key_manager_state = &(&state).into(); + let payment_intent = db + .find_payment_intent_by_merchant_reference_id_profile_id( + key_manager_state, + merchant_reference_id, + profile.get_id(), + &key_store, + &storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + let (payment_data, _req, customer) = Box::pin(payments_intent_operation_core::< + api::PaymentGetIntent, + _, + _, + PaymentIntentData, + >( + &state, + req_state, + merchant_account.clone(), + profile.clone(), + key_store.clone(), + operations::PaymentGetIntent, + api_models::payments::PaymentsGetIntentRequest { + id: payment_intent.get_id().clone(), + }, + payment_intent.get_id().clone(), + header_payload.clone(), + platform_merchant_account, + )) + .await?; + + transformers::ToResponse::< + api::PaymentGetIntent, + PaymentIntentData, + operations::PaymentGetIntent, + >::generate_response( + payment_data, + customer, + &state.base_url, + operations::PaymentGetIntent, + &state.conf.connector_request_reference_id_config, + None, + None, + header_payload.x_hs_latency, + &merchant_account, + ) +} + #[cfg(feature = "v2")] #[allow(clippy::too_many_arguments)] pub async fn payments_core( diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 7d4f16ee893..b8ecb2eea83 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1904,6 +1904,29 @@ impl PaymentIntentInterface for KafkaStore { ) .await } + + #[cfg(feature = "v2")] + async fn find_payment_intent_by_merchant_reference_id_profile_id( + &self, + state: &KeyManagerState, + merchant_reference_id: &id_type::PaymentReferenceId, + profile_id: &id_type::ProfileId, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: &MerchantStorageScheme, + ) -> error_stack::Result< + hyperswitch_domain_models::payments::PaymentIntent, + errors::DataStorageError, + > { + self.diesel_store + .find_payment_intent_by_merchant_reference_id_profile_id( + state, + merchant_reference_id, + profile_id, + merchant_key_store, + storage_scheme, + ) + .await + } } #[async_trait::async_trait] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index c2363500c04..e4da0119fdd 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -561,6 +561,12 @@ impl Payments { .route(web::post().to(payments::payments_create_intent)), ); + route = + route + .service(web::resource("/ref/{merchant_reference_id}").route( + web::get().to(payments::payment_get_intent_using_merchant_reference_id), + )); + route = route.service( web::scope("/{payment_id}") .service( diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index 895ba4b8aff..b09f92364bc 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -147,7 +147,8 @@ impl From for ApiIdentifier { | Flow::PaymentsPostSessionTokens | Flow::PaymentsUpdateIntent | Flow::PaymentsCreateAndConfirmIntent - | Flow::PaymentStartRedirection => Self::Payments, + | Flow::PaymentStartRedirection + | Flow::PaymentsRetrieveUsingMerchantReferenceId => Self::Payments, Flow::PayoutsCreate | Flow::PayoutsRetrieve diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index d461599b6f0..a088f04b095 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -2470,6 +2470,47 @@ pub async fn payment_status( .await } +#[cfg(feature = "v2")] +#[instrument(skip(state, req), fields(flow, payment_id))] +pub async fn payment_get_intent_using_merchant_reference_id( + state: web::Data, + req: actix_web::HttpRequest, + path: web::Path, +) -> impl Responder { + let flow = Flow::PaymentsRetrieveUsingMerchantReferenceId; + let header_payload = match HeaderPayload::foreign_try_from(req.headers()) { + Ok(headers) => headers, + Err(err) => { + return api::log_and_return_error_response(err); + } + }; + + let merchant_reference_id = path.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, auth: auth::AuthenticationData, _, req_state| async { + Box::pin(payments::payments_get_intent_using_merchant_reference( + state, + auth.merchant_account, + auth.profile, + auth.key_store, + req_state, + &merchant_reference_id, + header_payload.clone(), + auth.platform_merchant_account, + )) + .await + }, + &auth::HeaderAuth(auth::ApiKeyAuth), + api_locking::LockAction::NotApplicable, + )) + .await +} + #[cfg(feature = "v2")] #[instrument(skip_all, fields(flow = ?Flow::PaymentsRedirect, payment_id))] pub async fn payments_finish_redirection( diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index a267d24af1d..acd6e59431c 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -144,6 +144,8 @@ pub enum Flow { PaymentsRetrieve, /// Payments Retrieve force sync flow. PaymentsRetrieveForceSync, + /// Payments Retrieve using merchant reference id + PaymentsRetrieveUsingMerchantReferenceId, /// Payments update flow. PaymentsUpdate, /// Payments confirm flow. diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 3a564d958e9..0766d397bb7 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -185,4 +185,26 @@ impl PaymentIntentInterface for MockDb { Ok(payment_intent.clone()) } + #[cfg(feature = "v2")] + async fn find_payment_intent_by_merchant_reference_id_profile_id( + &self, + _state: &KeyManagerState, + merchant_reference_id: &common_utils::id_type::PaymentReferenceId, + profile_id: &common_utils::id_type::ProfileId, + _merchant_key_store: &MerchantKeyStore, + _storage_scheme: &common_enums::MerchantStorageScheme, + ) -> error_stack::Result { + let payment_intents = self.payment_intents.lock().await; + let payment_intent = payment_intents + .iter() + .find(|payment_intent| { + payment_intent.merchant_reference_id.as_ref() == Some(merchant_reference_id) + && payment_intent.profile_id.eq(profile_id) + }) + .ok_or(StorageError::ValueNotFound( + "PaymentIntent not found".to_string(), + ))?; + + Ok(payment_intent.clone()) + } } diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 786cbe75a43..5290c2bfca4 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -459,6 +459,32 @@ impl PaymentIntentInterface for KVRouterStore { ) .await } + #[cfg(feature = "v2")] + async fn find_payment_intent_by_merchant_reference_id_profile_id( + &self, + state: &KeyManagerState, + merchant_reference_id: &common_utils::id_type::PaymentReferenceId, + profile_id: &common_utils::id_type::ProfileId, + merchant_key_store: &MerchantKeyStore, + storage_scheme: &MerchantStorageScheme, + ) -> error_stack::Result { + match storage_scheme { + MerchantStorageScheme::PostgresOnly => { + self.router_store + .find_payment_intent_by_merchant_reference_id_profile_id( + state, + merchant_reference_id, + profile_id, + merchant_key_store, + storage_scheme, + ) + .await + } + MerchantStorageScheme::RedisKv => { + todo!() + } + } + } } #[async_trait::async_trait] @@ -622,6 +648,39 @@ impl PaymentIntentInterface for crate::RouterStore { .change_context(StorageError::DecryptionError) } + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn find_payment_intent_by_merchant_reference_id_profile_id( + &self, + state: &KeyManagerState, + merchant_reference_id: &common_utils::id_type::PaymentReferenceId, + profile_id: &common_utils::id_type::ProfileId, + merchant_key_store: &MerchantKeyStore, + _storage_scheme: &MerchantStorageScheme, + ) -> error_stack::Result { + let conn = pg_connection_read(self).await?; + let diesel_payment_intent = DieselPaymentIntent::find_by_merchant_reference_id_profile_id( + &conn, + merchant_reference_id, + profile_id, + ) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + })?; + let merchant_id = diesel_payment_intent.merchant_id.clone(); + + PaymentIntent::convert_back( + state, + diesel_payment_intent, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ) + .await + .change_context(StorageError::DecryptionError) + } + #[cfg(all(feature = "v1", feature = "olap"))] #[instrument(skip_all)] async fn filter_payment_intent_by_constraints( From 9b1b2455643d7a5744a4084fc1916c84634cb48d Mon Sep 17 00:00:00 2001 From: Uzair Khan Date: Thu, 6 Feb 2025 19:17:02 +0530 Subject: [PATCH 059/133] fix(dashboard_metadata): mask `poc_email` and `data_value` for DashboardMetadata (#7130) --- .../api_models/src/user/dashboard_metadata.rs | 2 +- .../src/user/dashboard_metadata.rs | 9 +++--- .../src/core/user/dashboard_metadata.rs | 9 ++++++ crates/router/src/services/email/types.rs | 2 +- .../src/utils/user/dashboard_metadata.rs | 29 ++++++++++--------- 5 files changed, 31 insertions(+), 20 deletions(-) diff --git a/crates/api_models/src/user/dashboard_metadata.rs b/crates/api_models/src/user/dashboard_metadata.rs index 1d606ef63e8..f15029a7a49 100644 --- a/crates/api_models/src/user/dashboard_metadata.rs +++ b/crates/api_models/src/user/dashboard_metadata.rs @@ -94,7 +94,7 @@ pub struct ProdIntent { pub business_label: Option, pub business_location: Option, pub display_name: Option, - pub poc_email: Option, + pub poc_email: Option>, pub business_type: Option, pub business_identifier: Option, pub business_website: Option, diff --git a/crates/diesel_models/src/user/dashboard_metadata.rs b/crates/diesel_models/src/user/dashboard_metadata.rs index 291b4d2a3e6..004fc86693b 100644 --- a/crates/diesel_models/src/user/dashboard_metadata.rs +++ b/crates/diesel_models/src/user/dashboard_metadata.rs @@ -1,5 +1,6 @@ use common_utils::id_type; use diesel::{query_builder::AsChangeset, Identifiable, Insertable, Queryable, Selectable}; +use masking::Secret; use time::PrimitiveDateTime; use crate::{enums, schema::dashboard_metadata}; @@ -12,7 +13,7 @@ pub struct DashboardMetadata { pub merchant_id: id_type::MerchantId, pub org_id: id_type::OrganizationId, pub data_key: enums::DashboardMetadata, - pub data_value: serde_json::Value, + pub data_value: Secret, pub created_by: String, pub created_at: PrimitiveDateTime, pub last_modified_by: String, @@ -28,7 +29,7 @@ pub struct DashboardMetadataNew { pub merchant_id: id_type::MerchantId, pub org_id: id_type::OrganizationId, pub data_key: enums::DashboardMetadata, - pub data_value: serde_json::Value, + pub data_value: Secret, pub created_by: String, pub created_at: PrimitiveDateTime, pub last_modified_by: String, @@ -41,7 +42,7 @@ pub struct DashboardMetadataNew { #[diesel(table_name = dashboard_metadata)] pub struct DashboardMetadataUpdateInternal { pub data_key: enums::DashboardMetadata, - pub data_value: serde_json::Value, + pub data_value: Secret, pub last_modified_by: String, pub last_modified_at: PrimitiveDateTime, } @@ -50,7 +51,7 @@ pub struct DashboardMetadataUpdateInternal { pub enum DashboardMetadataUpdate { UpdateData { data_key: enums::DashboardMetadata, - data_value: serde_json::Value, + data_value: Secret, last_modified_by: String, }, } diff --git a/crates/router/src/core/user/dashboard_metadata.rs b/crates/router/src/core/user/dashboard_metadata.rs index 689762c1f43..2ea2ba1d3d8 100644 --- a/crates/router/src/core/user/dashboard_metadata.rs +++ b/crates/router/src/core/user/dashboard_metadata.rs @@ -1,12 +1,16 @@ +use std::str::FromStr; + use api_models::user::dashboard_metadata::{self as api, GetMultipleMetaDataPayload}; #[cfg(feature = "email")] use common_enums::EntityType; +use common_utils::pii; use diesel_models::{ enums::DashboardMetadata as DBEnum, user::dashboard_metadata::DashboardMetadata, }; use error_stack::{report, ResultExt}; #[cfg(feature = "email")] use masking::ExposeInterface; +use masking::PeekInterface; #[cfg(feature = "email")] use router_env::logger; @@ -447,6 +451,11 @@ async fn insert_metadata( metadata } types::MetaData::ProdIntent(data) => { + if let Some(poc_email) = &data.poc_email { + let inner_poc_email = poc_email.peek().as_str(); + pii::Email::from_str(inner_poc_email) + .change_context(UserErrors::EmailParsingError)?; + } let mut metadata = utils::insert_user_scoped_metadata_to_db( state, user.user_id.clone(), diff --git a/crates/router/src/services/email/types.rs b/crates/router/src/services/email/types.rs index 476cd1292f6..1b88f648678 100644 --- a/crates/router/src/services/email/types.rs +++ b/crates/router/src/services/email/types.rs @@ -454,7 +454,7 @@ impl BizEmailProd { settings: state.conf.clone(), subject: consts::user::EMAIL_SUBJECT_NEW_PROD_INTENT, user_name: data.poc_name.unwrap_or_default().into(), - poc_email: data.poc_email.unwrap_or_default().into(), + poc_email: data.poc_email.unwrap_or_default(), legal_business_name: data.legal_business_name.unwrap_or_default(), business_location: data .business_location diff --git a/crates/router/src/utils/user/dashboard_metadata.rs b/crates/router/src/utils/user/dashboard_metadata.rs index b5e16290fca..f5836f25fa7 100644 --- a/crates/router/src/utils/user/dashboard_metadata.rs +++ b/crates/router/src/utils/user/dashboard_metadata.rs @@ -10,7 +10,7 @@ use diesel_models::{ user::dashboard_metadata::{DashboardMetadata, DashboardMetadataNew, DashboardMetadataUpdate}, }; use error_stack::{report, ResultExt}; -use masking::Secret; +use masking::{ExposeInterface, PeekInterface, Secret}; use router_env::logger; use crate::{ @@ -37,7 +37,7 @@ pub async fn insert_merchant_scoped_metadata_to_db( merchant_id, org_id, data_key: metadata_key, - data_value, + data_value: Secret::from(data_value), created_by: user_id.clone(), created_at: now, last_modified_by: user_id, @@ -70,7 +70,7 @@ pub async fn insert_user_scoped_metadata_to_db( merchant_id, org_id, data_key: metadata_key, - data_value, + data_value: Secret::from(data_value), created_by: user_id.clone(), created_at: now, last_modified_by: user_id, @@ -143,7 +143,7 @@ pub async fn update_merchant_scoped_metadata( metadata_key, DashboardMetadataUpdate::UpdateData { data_key: metadata_key, - data_value, + data_value: Secret::from(data_value), last_modified_by: user_id, }, ) @@ -171,7 +171,7 @@ pub async fn update_user_scoped_metadata( metadata_key, DashboardMetadataUpdate::UpdateData { data_key: metadata_key, - data_value, + data_value: Secret::from(data_value), last_modified_by: user_id, }, ) @@ -183,7 +183,7 @@ pub fn deserialize_to_response(data: Option<&DashboardMetadata>) -> UserResul where T: serde::de::DeserializeOwned, { - data.map(|metadata| serde_json::from_value(metadata.data_value.clone())) + data.map(|metadata| serde_json::from_value(metadata.data_value.clone().expose())) .transpose() .change_context(UserErrors::InternalServerError) .attach_printable("Error Serializing Metadata from DB") @@ -278,17 +278,18 @@ pub fn parse_string_to_enums(query: String) -> UserResult, value_to_be_checked: &str) -> bool { - value - .as_ref() - .is_some_and(|mail| !mail.contains(value_to_be_checked)) +fn not_contains_string(value: Option<&str>, value_to_be_checked: &str) -> bool { + value.is_some_and(|mail| !mail.contains(value_to_be_checked)) } pub fn is_prod_email_required(data: &ProdIntent, user_email: String) -> bool { - let poc_email_check = not_contains_string(&data.poc_email, "juspay"); - let business_website_check = not_contains_string(&data.business_website, "juspay") - && not_contains_string(&data.business_website, "hyperswitch"); - let user_email_check = not_contains_string(&Some(user_email), "juspay"); + let poc_email_check = not_contains_string( + data.poc_email.as_ref().map(|email| email.peek().as_str()), + "juspay", + ); + let business_website_check = not_contains_string(data.business_website.as_deref(), "juspay") + && not_contains_string(data.business_website.as_deref(), "hyperswitch"); + let user_email_check = not_contains_string(Some(&user_email), "juspay"); if (poc_email_check && business_website_check && user_email_check).not() { logger::info!(prod_intent_email = poc_email_check); From f2117542a7dda4dbfa768fdb24229c113e25c93e Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Thu, 6 Feb 2025 19:17:11 +0530 Subject: [PATCH 060/133] feat(router): add `organization_id` in authentication table and add it in authentication events (#7168) --- crates/analytics/docs/clickhouse/scripts/authentications.sql | 4 ++++ crates/diesel_models/src/authentication.rs | 2 ++ crates/diesel_models/src/schema.rs | 2 ++ crates/diesel_models/src/schema_v2.rs | 2 ++ crates/router/src/core/authentication.rs | 3 +++ crates/router/src/core/authentication/utils.rs | 3 +++ crates/router/src/core/payments/operations/payment_confirm.rs | 3 +++ crates/router/src/core/unified_authentication_service.rs | 2 ++ crates/router/src/db/authentication.rs | 1 + crates/router/src/services/kafka/authentication.rs | 2 ++ crates/router/src/services/kafka/authentication_event.rs | 2 ++ .../down.sql | 3 +++ .../up.sql | 3 +++ 13 files changed, 32 insertions(+) create mode 100644 migrations/2025-01-30-111507_add_organization_id_in_authentication/down.sql create mode 100644 migrations/2025-01-30-111507_add_organization_id_in_authentication/up.sql diff --git a/crates/analytics/docs/clickhouse/scripts/authentications.sql b/crates/analytics/docs/clickhouse/scripts/authentications.sql index 0d540e64942..7fb00995e94 100644 --- a/crates/analytics/docs/clickhouse/scripts/authentications.sql +++ b/crates/analytics/docs/clickhouse/scripts/authentications.sql @@ -35,6 +35,7 @@ CREATE TABLE authentication_queue ( `ds_trans_id` Nullable(String), `directory_server_id` Nullable(String), `acquirer_country_code` Nullable(String), + `organization_id` String, `sign_flag` Int8 ) ENGINE = Kafka SETTINGS kafka_broker_list = 'kafka0:29092', kafka_topic_list = 'hyperswitch-authentication-events', @@ -80,6 +81,7 @@ CREATE TABLE authentications ( `ds_trans_id` Nullable(String), `directory_server_id` Nullable(String), `acquirer_country_code` Nullable(String), + `organization_id` String, `sign_flag` Int8, INDEX authenticationConnectorIndex authentication_connector TYPE bloom_filter GRANULARITY 1, INDEX transStatusIndex trans_status TYPE bloom_filter GRANULARITY 1, @@ -127,6 +129,7 @@ CREATE MATERIALIZED VIEW authentication_mv TO authentications ( `ds_trans_id` Nullable(String), `directory_server_id` Nullable(String), `acquirer_country_code` Nullable(String), + `organization_id` String, `sign_flag` Int8 ) AS SELECT @@ -167,6 +170,7 @@ SELECT ds_trans_id, directory_server_id, acquirer_country_code, + organization_id, sign_flag FROM authentication_queue diff --git a/crates/diesel_models/src/authentication.rs b/crates/diesel_models/src/authentication.rs index c79892d27bf..3e0039f4ac5 100644 --- a/crates/diesel_models/src/authentication.rs +++ b/crates/diesel_models/src/authentication.rs @@ -48,6 +48,7 @@ pub struct Authentication { pub directory_server_id: Option, pub acquirer_country_code: Option, pub service_details: Option, + pub organization_id: common_utils::id_type::OrganizationId, } impl Authentication { @@ -96,6 +97,7 @@ pub struct AuthenticationNew { pub directory_server_id: Option, pub acquirer_country_code: Option, pub service_details: Option, + pub organization_id: common_utils::id_type::OrganizationId, } #[derive(Debug)] diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index e748e5beca2..170eb576e34 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -121,6 +121,8 @@ diesel::table! { #[max_length = 64] acquirer_country_code -> Nullable, service_details -> Nullable, + #[max_length = 32] + organization_id -> Varchar, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 5bd3195f43d..19456a5c65b 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -122,6 +122,8 @@ diesel::table! { #[max_length = 64] acquirer_country_code -> Nullable, service_details -> Nullable, + #[max_length = 32] + organization_id -> Varchar, } } diff --git a/crates/router/src/core/authentication.rs b/crates/router/src/core/authentication.rs index bedd7787a73..e6c27748170 100644 --- a/crates/router/src/core/authentication.rs +++ b/crates/router/src/core/authentication.rs @@ -124,6 +124,7 @@ pub async fn perform_post_authentication( } } +#[allow(clippy::too_many_arguments)] pub async fn perform_pre_authentication( state: &SessionState, key_store: &domain::MerchantKeyStore, @@ -132,6 +133,7 @@ pub async fn perform_pre_authentication( business_profile: &domain::Profile, acquirer_details: Option, payment_id: Option, + organization_id: common_utils::id_type::OrganizationId, ) -> CustomResult { let (authentication_connector, three_ds_connector_account) = utils::get_authentication_connector_data(state, key_store, business_profile).await?; @@ -147,6 +149,7 @@ pub async fn perform_pre_authentication( .get_mca_id() .ok_or(ApiErrorResponse::InternalServerError) .attach_printable("Error while finding mca_id from merchant_connector_account")?, + organization_id, ) .await?; diff --git a/crates/router/src/core/authentication/utils.rs b/crates/router/src/core/authentication/utils.rs index 21c2ae20e2c..1d742f939ec 100644 --- a/crates/router/src/core/authentication/utils.rs +++ b/crates/router/src/core/authentication/utils.rs @@ -174,6 +174,7 @@ impl ForeignFrom for common_enums::AttemptSt } } +#[allow(clippy::too_many_arguments)] pub async fn create_new_authentication( state: &SessionState, merchant_id: common_utils::id_type::MerchantId, @@ -182,6 +183,7 @@ pub async fn create_new_authentication( profile_id: common_utils::id_type::ProfileId, payment_id: Option, merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + organization_id: common_utils::id_type::OrganizationId, ) -> RouterResult { let authentication_id = common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); @@ -220,6 +222,7 @@ pub async fn create_new_authentication( directory_server_id: None, acquirer_country_code: None, service_details: None, + organization_id, }; state .store diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 5f3b515c96e..64191f77c6d 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -983,6 +983,7 @@ impl Domain> for business_profile, Some(acquirer_details), Some(payment_data.payment_attempt.payment_id.clone()), + payment_data.payment_attempt.organization_id.clone(), ) .await?; if authentication.is_separate_authn_required() @@ -1175,6 +1176,7 @@ impl Domain> for &authentication_id, payment_data.service_details.clone(), authentication_status, + payment_data.payment_attempt.organization_id.clone(), ) .await?; }, @@ -1197,6 +1199,7 @@ impl Domain> for .get_mca_id() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("Error while finding mca_id from merchant_connector_account")?, + payment_data.payment_attempt.organization_id.clone(), ) .await?; diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index d9c36aeaa3d..28d32f627cb 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -410,6 +410,7 @@ pub async fn create_new_authentication( authentication_id: &str, service_details: Option, authentication_status: common_enums::AuthenticationStatus, + organization_id: common_utils::id_type::OrganizationId, ) -> RouterResult { let service_details_value = service_details .map(serde_json::to_value) @@ -453,6 +454,7 @@ pub async fn create_new_authentication( directory_server_id: None, acquirer_country_code: None, service_details: service_details_value, + organization_id, }; state .store diff --git a/crates/router/src/db/authentication.rs b/crates/router/src/db/authentication.rs index af3da68e6ae..7c98b90134e 100644 --- a/crates/router/src/db/authentication.rs +++ b/crates/router/src/db/authentication.rs @@ -151,6 +151,7 @@ impl AuthenticationInterface for MockDb { directory_server_id: authentication.directory_server_id, acquirer_country_code: authentication.acquirer_country_code, service_details: authentication.service_details, + organization_id: authentication.organization_id, }; authentications.push(authentication.clone()); Ok(authentication) diff --git a/crates/router/src/services/kafka/authentication.rs b/crates/router/src/services/kafka/authentication.rs index 5cf2d066ee2..86652c8c76e 100644 --- a/crates/router/src/services/kafka/authentication.rs +++ b/crates/router/src/services/kafka/authentication.rs @@ -41,6 +41,7 @@ pub struct KafkaAuthentication<'a> { pub ds_trans_id: Option<&'a String>, pub directory_server_id: Option<&'a String>, pub acquirer_country_code: Option<&'a String>, + pub organization_id: &'a common_utils::id_type::OrganizationId, } impl<'a> KafkaAuthentication<'a> { @@ -82,6 +83,7 @@ impl<'a> KafkaAuthentication<'a> { ds_trans_id: authentication.ds_trans_id.as_ref(), directory_server_id: authentication.directory_server_id.as_ref(), acquirer_country_code: authentication.acquirer_country_code.as_ref(), + organization_id: &authentication.organization_id, } } } diff --git a/crates/router/src/services/kafka/authentication_event.rs b/crates/router/src/services/kafka/authentication_event.rs index 4169ff7b42a..a875b4bb492 100644 --- a/crates/router/src/services/kafka/authentication_event.rs +++ b/crates/router/src/services/kafka/authentication_event.rs @@ -42,6 +42,7 @@ pub struct KafkaAuthenticationEvent<'a> { pub ds_trans_id: Option<&'a String>, pub directory_server_id: Option<&'a String>, pub acquirer_country_code: Option<&'a String>, + pub organization_id: &'a common_utils::id_type::OrganizationId, } impl<'a> KafkaAuthenticationEvent<'a> { @@ -83,6 +84,7 @@ impl<'a> KafkaAuthenticationEvent<'a> { ds_trans_id: authentication.ds_trans_id.as_ref(), directory_server_id: authentication.directory_server_id.as_ref(), acquirer_country_code: authentication.acquirer_country_code.as_ref(), + organization_id: &authentication.organization_id, } } } diff --git a/migrations/2025-01-30-111507_add_organization_id_in_authentication/down.sql b/migrations/2025-01-30-111507_add_organization_id_in_authentication/down.sql new file mode 100644 index 00000000000..1f04d19cf25 --- /dev/null +++ b/migrations/2025-01-30-111507_add_organization_id_in_authentication/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE authentication + DROP COLUMN IF EXISTS organization_id; \ No newline at end of file diff --git a/migrations/2025-01-30-111507_add_organization_id_in_authentication/up.sql b/migrations/2025-01-30-111507_add_organization_id_in_authentication/up.sql new file mode 100644 index 00000000000..f08285b375f --- /dev/null +++ b/migrations/2025-01-30-111507_add_organization_id_in_authentication/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TABLE authentication + ADD COLUMN IF NOT EXISTS organization_id VARCHAR(32) NOT NULL DEFAULT 'default_org'; \ No newline at end of file From 2d0ac8d46d2ecfd7287b67b646bc0b284ed838a9 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 6 Feb 2025 19:17:56 +0530 Subject: [PATCH 061/133] chore(connectors): [fiuu] update pm_filters for apple pay and google pay (#7182) --- config/deployments/integration_test.toml | 4 +++- config/deployments/production.toml | 4 +++- config/deployments/sandbox.toml | 4 +++- config/development.toml | 2 ++ 4 files changed, 11 insertions(+), 3 deletions(-) diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 4d0bce877e8..e8da3489b67 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -382,7 +382,9 @@ red_pagos = { country = "UY", currency = "UYU" } local_bank_transfer = { country = "CN", currency = "CNY" } [pm_filters.fiuu] -duit_now = { country ="MY", currency = "MYR" } +duit_now = { country = "MY", currency = "MYR" } +apple_pay = { country = "MY", currency = "MYR" } +google_pay = { country = "MY", currency = "MYR" } [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 8964fbc2e20..7e71767ee5f 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -398,7 +398,9 @@ local_bank_transfer = { country = "CN", currency = "CNY" } [pm_filters.fiuu] -duit_now = { country ="MY", currency = "MYR" } +duit_now = { country = "MY", currency = "MYR" } +apple_pay = { country = "MY", currency = "MYR" } +google_pay = { country = "MY", currency = "MYR" } [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index e98bd1e2fad..d6477a4aab3 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -400,7 +400,9 @@ local_bank_transfer = { country = "CN", currency = "CNY" } [pm_filters.fiuu] -duit_now = { country ="MY", currency = "MYR" } +duit_now = { country = "MY", currency = "MYR" } +apple_pay = { country = "MY", currency = "MYR" } +google_pay = { country = "MY", currency = "MYR" } [payout_method_filters.adyenplatform] sepa = { country = "ES,SK,AT,NL,DE,BE,FR,FI,PT,IE,EE,LT,LV,IT,CZ,DE,HU,NO,PL,SE,GB,CH" , currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } diff --git a/config/development.toml b/config/development.toml index f61321fda0f..660e93df12f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -566,6 +566,8 @@ debit = { currency = "USD" } [pm_filters.fiuu] duit_now = { country = "MY", currency = "MYR" } +apple_pay = { country = "MY", currency = "MYR" } +google_pay = { country = "MY", currency = "MYR" } [pm_filters.inespay] sepa = { currency = "EUR" } From ddc64faf7744cd4254f6ef1d4333b89e659d7a68 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 7 Feb 2025 00:30:45 +0000 Subject: [PATCH 062/133] chore(version): 2025.02.07.0 --- CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57524ca4ddd..865d2b7e020 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,36 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.07.0 + +### Features + +- **connector:** [COINGATE] Add Template PR ([#7052](https://github.com/juspay/hyperswitch/pull/7052)) ([`dddb1b0`](https://github.com/juspay/hyperswitch/commit/dddb1b06bea4ac89d838641508728d2da4326ba1)) +- **core:** Add support for v2 payments get intent using merchant reference id ([#7123](https://github.com/juspay/hyperswitch/pull/7123)) ([`e17ffd1`](https://github.com/juspay/hyperswitch/commit/e17ffd1257adc1618ed60dee81ea1e7df84cb3d5)) +- **router:** Add `organization_id` in authentication table and add it in authentication events ([#7168](https://github.com/juspay/hyperswitch/pull/7168)) ([`f211754`](https://github.com/juspay/hyperswitch/commit/f2117542a7dda4dbfa768fdb24229c113e25c93e)) +- **routing:** Contract based routing integration ([#6761](https://github.com/juspay/hyperswitch/pull/6761)) ([`60ddddf`](https://github.com/juspay/hyperswitch/commit/60ddddf24a1625b8044c095c5d01754022102813)) + +### Bug Fixes + +- **connector:** Handle unexpected error response from bluesnap connector ([#7120](https://github.com/juspay/hyperswitch/pull/7120)) ([`8ae5267`](https://github.com/juspay/hyperswitch/commit/8ae5267b91cfb37b14df1acf5fd7dfc2570b58ce)) +- **dashboard_metadata:** Mask `poc_email` and `data_value` for DashboardMetadata ([#7130](https://github.com/juspay/hyperswitch/pull/7130)) ([`9b1b245`](https://github.com/juspay/hyperswitch/commit/9b1b2455643d7a5744a4084fc1916c84634cb48d)) + +### Refactors + +- **customer:** Return redacted customer instead of error ([#7122](https://github.com/juspay/hyperswitch/pull/7122)) ([`97e9270`](https://github.com/juspay/hyperswitch/commit/97e9270ed4458a24207ea5434d65c54fb4b6237d)) +- **dynamic_fields:** Dynamic fields for Adyen and Stripe, renaming klarnaCheckout, WASM for KlarnaCheckout ([#7015](https://github.com/juspay/hyperswitch/pull/7015)) ([`a6367d9`](https://github.com/juspay/hyperswitch/commit/a6367d92f629ef01cdb73aded8a81d2ba198f38c)) +- **router:** Store `network_transaction_id` for `off_session` payments irrespective of the `is_connector_agnostic_mit_enabled` config ([#7083](https://github.com/juspay/hyperswitch/pull/7083)) ([`f9a4713`](https://github.com/juspay/hyperswitch/commit/f9a4713a60028e26b98143c6296d9969cd090163)) + +### Miscellaneous Tasks + +- **connector:** [Fiuu] log keys in the PSync response ([#7189](https://github.com/juspay/hyperswitch/pull/7189)) ([`c044fff`](https://github.com/juspay/hyperswitch/commit/c044ffff0c47ee5d3ef5f905c3f590fae4ac9a24)) +- **connectors:** [fiuu] update pm_filters for apple pay and google pay ([#7182](https://github.com/juspay/hyperswitch/pull/7182)) ([`2d0ac8d`](https://github.com/juspay/hyperswitch/commit/2d0ac8d46d2ecfd7287b67b646bc0b284ed838a9)) +- **roles:** Remove redundant variant from PermissionGroup ([#6985](https://github.com/juspay/hyperswitch/pull/6985)) ([`775dcc5`](https://github.com/juspay/hyperswitch/commit/775dcc5a4e3b41dd1e4d0e4c47eccca15a8a4b3a)) + +**Full Changelog:** [`2025.02.06.0...2025.02.07.0`](https://github.com/juspay/hyperswitch/compare/2025.02.06.0...2025.02.07.0) + +- - - + ## 2025.02.06.0 ### Features From 4693d21b7c26055ed33fadd3f53943715ab71516 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Fri, 7 Feb 2025 10:19:40 +0530 Subject: [PATCH 063/133] feat(connector): [DataTrans] ADD 3DS Flow (#6026) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 4 +- config/deployments/integration_test.toml | 5 +- config/deployments/production.toml | 5 +- config/deployments/sandbox.toml | 5 +- config/development.toml | 5 +- config/docker_compose.toml | 5 +- .../src/connectors/datatrans.rs | 55 +++-- .../src/connectors/datatrans/transformers.rs | 198 +++++++++++++++--- .../src/connectors/paybox/transformers.rs | 4 +- crates/hyperswitch_connectors/src/utils.rs | 4 + crates/hyperswitch_interfaces/src/configs.rs | 2 +- .../payment_connector_required_fields.rs | 148 +++++++++++++ loadtest/config/development.toml | 5 +- 13 files changed, 383 insertions(+), 62 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 7bb6d8fc567..16b5ef6c3a1 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -200,6 +200,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -452,7 +453,8 @@ slack_invite_url = "https://www.example.com/" # Slack invite url for hyperswit discord_invite_url = "https://www.example.com/" # Discord invite url for hyperswitch [mandates.supported_payment_methods] -card.credit = { connector_list = "stripe,adyen,cybersource,bankofamerica" } # Mandate supported payment method type and connector for card +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" wallet.paypal = { connector_list = "adyen" } # Mandate supported payment method type and connector for wallets pay_later.klarna = { connector_list = "adyen" } # Mandate supported payment method type and connector for pay_later bank_debit.ach = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index e8da3489b67..01e74f36542 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -46,6 +46,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -166,8 +167,8 @@ bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } bank_debit.bacs = { connector_list = "stripe,gocardless" } bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" wallet.samsung_pay.connector_list = "cybersource" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 7e71767ee5f..e52d1a1f91a 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -50,6 +50,7 @@ coingate.base_url = "https://api.coingate.com/v2" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" +datatrans.secondary_base_url = "https://pay.datatrans.com/" deutschebank.base_url = "https://merch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -166,8 +167,8 @@ bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } bank_debit.bacs = { connector_list = "stripe,gocardless" } bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" wallet.samsung_pay.connector_list = "cybersource" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index d6477a4aab3..548f4aead63 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -50,6 +50,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -166,8 +167,8 @@ bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } bank_debit.bacs = { connector_list = "stripe,gocardless" } bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" wallet.samsung_pay.connector_list = "cybersource" diff --git a/config/development.toml b/config/development.toml index 660e93df12f..fc7a9427d8c 100644 --- a/config/development.toml +++ b/config/development.toml @@ -220,6 +220,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -631,8 +632,8 @@ bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } bank_debit.bacs = { connector_list = "stripe,gocardless" } bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" wallet.samsung_pay.connector_list = "cybersource" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ed8f99d20df..060093550e9 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -132,6 +132,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -529,8 +530,8 @@ wallet.google_pay = { connector_list = "stripe,adyen,bankofamerica" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.samsung_pay = { connector_list = "cybersource" } wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } +card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } +card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica" } bank_debit.ach = { connector_list = "gocardless,adyen" } bank_debit.becs = { connector_list = "gocardless" } bank_debit.bacs = { connector_list = "adyen" } diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans.rs b/crates/hyperswitch_connectors/src/connectors/datatrans.rs index 2a19a99c0fa..b549f48984e 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans.rs @@ -35,6 +35,7 @@ use hyperswitch_interfaces::{ ConnectorValidation, }, configs::Connectors, + consts::NO_ERROR_CODE, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, @@ -46,7 +47,7 @@ use transformers as datatrans; use crate::{ constants::headers, types::ResponseRouterData, - utils::{convert_amount, RefundsRequestData}, + utils::{self, convert_amount, RefundsRequestData, RouterData as OtherRouterData}, }; impl api::Payment for Datatrans {} @@ -136,21 +137,35 @@ impl ConnectorCommon for Datatrans { res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: datatrans::DatatransErrorResponse = res - .response - .parse_struct("DatatransErrorResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - Ok(ErrorResponse { - status_code: res.status_code, - code: response.error.code, - message: response.error.message.clone(), - reason: Some(response.error.message.clone()), - attempt_status: None, - connector_transaction_id: None, - }) + let (cow, _, _) = encoding_rs::ISO_8859_10.decode(&res.response); + let response = cow.as_ref().to_string(); + if utils::is_html_response(&response) { + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(ErrorResponse { + status_code: res.status_code, + code: NO_ERROR_CODE.to_owned(), + message: response.clone(), + reason: Some(response), + attempt_status: None, + connector_transaction_id: None, + }) + } else { + let response: datatrans::DatatransErrorResponse = res + .response + .parse_struct("DatatransErrorType") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.code.clone(), + message: response.error.message.clone(), + reason: Some(response.error.message.clone()), + attempt_status: None, + connector_transaction_id: None, + }) + } } } @@ -204,11 +219,15 @@ impl ConnectorIntegration CustomResult { let base_url = self.base_url(connectors); - Ok(format!("{base_url}v1/transactions/authorize")) + if req.is_three_ds() { + Ok(format!("{base_url}v1/transactions")) + } else { + Ok(format!("{base_url}v1/transactions/authorize")) + } } fn get_request_body( diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index 2ec76cb2937..c0f4ed22722 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -1,14 +1,19 @@ +use std::collections::HashMap; + use common_enums::enums; -use common_utils::types::MinorUnit; +use common_utils::{pii::Email, request::Method, types::MinorUnit}; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{Card, PaymentMethodData}, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsAuthorizeData, ResponseId}, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types, }; -use hyperswitch_interfaces::errors; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::Secret; use serde::{Deserialize, Serialize}; @@ -18,12 +23,15 @@ use crate::{ PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, }, utils::{ - get_unimplemented_payment_method_error_message, CardData as _, PaymentsAuthorizeRequestData, + get_unimplemented_payment_method_error_message, AddressDetailsData, CardData as _, + PaymentsAuthorizeRequestData, RouterData as _, }, }; const TRANSACTION_ALREADY_CANCELLED: &str = "transaction already canceled"; const TRANSACTION_ALREADY_SETTLED: &str = "already settled"; +const REDIRECTION_SBX_URL: &str = "https://pay.sandbox.datatrans.com"; +const REDIRECTION_PROD_URL: &str = "https://pay.datatrans.com"; #[derive(Default, Debug, Clone, Serialize, Deserialize)] pub struct DatatransErrorResponse { @@ -47,16 +55,25 @@ pub struct DatatransPaymentsRequest { pub card: PlainCardDetails, pub refno: String, pub auto_settle: bool, + #[serde(skip_serializing_if = "Option::is_none")] + pub redirect: Option, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct RedirectUrls { + pub success_url: Option, + pub cancel_url: Option, + pub error_url: Option, +} +#[derive(Debug, Deserialize, Clone, Serialize)] #[serde(rename_all = "snake_case")] pub enum TransactionType { Payment, Credit, CardCheck, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Clone, Serialize)] #[serde(rename_all = "snake_case")] pub enum TransactionStatus { Initialized, @@ -68,7 +85,7 @@ pub enum TransactionStatus { Failed, } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Debug, Deserialize, Clone, Serialize)] #[serde(untagged)] pub enum DatatransSyncResponse { Error(DatatransError), @@ -86,13 +103,26 @@ pub enum DataTransCancelResponse { Empty, } -#[derive(Serialize, Deserialize, Debug)] +#[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct SyncResponse { pub transaction_id: String, #[serde(rename = "type")] pub res_type: TransactionType, pub status: TransactionStatus, + pub detail: SyncDetails, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SyncDetails { + fail: Option, +} +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct FailDetails { + reason: Option, + message: Option, } #[derive(Serialize, Clone, Debug)] @@ -103,6 +133,32 @@ pub struct PlainCardDetails { pub number: cards::CardNumber, pub expiry_month: Secret, pub expiry_year: Secret, + pub cvv: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "3D")] + pub three_ds: Option, +} + +#[derive(Serialize, Clone, Debug)] +pub struct ThreedsInfo { + cardholder: CardHolder, +} + +#[derive(Debug, Serialize, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardHolder { + cardholder_name: Secret, + email: Email, + #[serde(skip_serializing_if = "Option::is_none")] + bill_addr_line1: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bill_addr_post_code: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bill_addr_city: Option, + #[serde(skip_serializing_if = "Option::is_none")] + bill_addr_state: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + bill_addr_country: Option, } #[derive(Debug, Clone, Serialize, Default, Deserialize)] @@ -116,6 +172,7 @@ pub struct DatatransError { pub enum DatatransResponse { TransactionResponse(DatatransSuccessResponse), ErrorResponse(DatatransError), + ThreeDSResponse(Datatrans3DSResponse), } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -131,6 +188,20 @@ pub enum DatatransRefundsResponse { Error(DatatransError), } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct Datatrans3DSResponse { + pub transaction_id: String, + #[serde(rename = "3D")] + pub three_ds_enrolled: ThreeDSEnolled, +} + +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSEnolled { + pub enrolled: bool, +} + #[derive(Default, Debug, Serialize)] pub struct DatatransRefundRequest { pub amount: MinorUnit, @@ -166,17 +237,17 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> PaymentMethodData::Card(req_card) => Ok(Self { amount: item.amount, currency: item.router_data.request.currency, - card: PlainCardDetails { - res_type: "PLAIN".to_string(), - number: req_card.card_number.clone(), - expiry_month: req_card.card_exp_month.clone(), - expiry_year: req_card.get_card_expiry_year_2_digit()?, - }, + card: create_card_details(item, &req_card)?, refno: item.router_data.connector_request_reference_id.clone(), - auto_settle: matches!( - item.router_data.request.capture_method, - Some(enums::CaptureMethod::Automatic) - ), + auto_settle: item.router_data.request.is_auto_capture()?, + redirect: match item.router_data.is_three_ds() { + true => Some(RedirectUrls { + success_url: item.router_data.request.router_return_url.clone(), + cancel_url: item.router_data.request.router_return_url.clone(), + error_url: item.router_data.request.router_return_url.clone(), + }), + false => None, + }, }), PaymentMethodData::Wallet(_) | PaymentMethodData::PayLater(_) @@ -226,9 +297,40 @@ fn get_status(item: &DatatransResponse, is_auto_capture: bool) -> enums::Attempt enums::AttemptStatus::Authorized } } + DatatransResponse::ThreeDSResponse(_) => enums::AttemptStatus::AuthenticationPending, } } +fn create_card_details( + item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, + card: &Card, +) -> Result> { + let mut details = PlainCardDetails { + res_type: "PLAIN".to_string(), + number: card.card_number.clone(), + expiry_month: card.card_exp_month.clone(), + expiry_year: card.get_card_expiry_year_2_digit()?, + cvv: card.card_cvc.clone(), + three_ds: None, + }; + + if item.router_data.is_three_ds() { + let billing = item.router_data.get_billing_address()?; + details.three_ds = Some(ThreedsInfo { + cardholder: CardHolder { + cardholder_name: item.router_data.get_billing_full_name()?, + email: item.router_data.request.get_email()?, + bill_addr_line1: billing.get_line1().ok().cloned(), + bill_addr_post_code: billing.get_zip().ok().cloned(), + bill_addr_city: billing.get_city().ok().cloned(), + bill_addr_state: billing.get_state().ok().cloned(), + bill_addr_country: billing.get_country().ok().copied(), + }, + }); + } + Ok(details) +} + impl From for enums::AttemptStatus { fn from(item: SyncResponse) -> Self { match item.res_type { @@ -292,6 +394,28 @@ impl charge_id: None, }) } + DatatransResponse::ThreeDSResponse(response) => { + let redirection_link = match item.data.test_mode { + Some(true) => format!("{}/v1/start", REDIRECTION_SBX_URL), + Some(false) | None => format!("{}/v1/start", REDIRECTION_PROD_URL), + }; + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.transaction_id.clone(), + ), + redirection_data: Box::new(Some(RedirectForm::Form { + endpoint: format!("{}/{}", redirection_link, response.transaction_id), + method: Method::Get, + form_fields: HashMap::new(), + })), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }) + } }; Ok(Self { status, @@ -394,13 +518,29 @@ impl TryFrom> ..item.data }) } - DatatransSyncResponse::Response(response) => { - let resource_id = - ResponseId::ConnectorTransactionId(response.transaction_id.to_string()); - Ok(Self { - status: enums::AttemptStatus::from(response), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id, + DatatransSyncResponse::Response(sync_response) => { + let status = enums::AttemptStatus::from(sync_response.clone()); + let response = if status == enums::AttemptStatus::Failure { + let (code, message) = match sync_response.detail.fail { + Some(fail_details) => ( + fail_details.reason.unwrap_or(NO_ERROR_CODE.to_string()), + fail_details.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), + ), + None => (NO_ERROR_CODE.to_string(), NO_ERROR_MESSAGE.to_string()), + }; + Err(ErrorResponse { + code, + message: message.clone(), + reason: Some(message), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: None, + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + sync_response.transaction_id.to_string(), + ), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -408,7 +548,11 @@ impl TryFrom> connector_response_reference_id: None, incremental_authorization_allowed: None, charge_id: None, - }), + }) + }; + Ok(Self { + status, + response, ..item.data }) } diff --git a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs index 9d01036f660..70635e47300 100644 --- a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs @@ -615,9 +615,7 @@ pub fn parse_paybox_response( ) -> CustomResult { let (cow, _, _) = encoding_rs::ISO_8859_15.decode(&query_bytes); let response_str = cow.as_ref().trim(); - if (response_str.starts_with("") || response_str.starts_with("")) - && is_three_ds - { + if utils::is_html_response(response_str) && is_three_ds { let response = response_str.to_string(); return Ok(if response.contains("Erreur 201") { PayboxResponse::Error(response) diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 70fe780ea64..5f9820070c1 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2665,6 +2665,10 @@ pub fn deserialize_xml_to_struct( Ok(result) } +pub fn is_html_response(response: &str) -> bool { + response.starts_with("") || response.starts_with("") +} + #[cfg(feature = "payouts")] pub trait PayoutsData { fn get_transfer_id(&self) -> Result; diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index c832507f92e..ecb3aea102a 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -31,7 +31,7 @@ pub struct Connectors { pub cryptopay: ConnectorParams, pub ctp_mastercard: NoParams, pub cybersource: ConnectorParams, - pub datatrans: ConnectorParams, + pub datatrans: ConnectorParamsWithSecondaryBaseUrl, pub deutschebank: ConnectorParams, pub digitalvirgo: ConnectorParams, pub dlocal: ConnectorParams, diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 46076b057eb..c5b9b9a0430 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -950,6 +950,80 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Datatrans, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), ( enums::Connector::Deutschebank, RequiredFieldFinal { @@ -4336,6 +4410,80 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Datatrans, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "card_holder_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ] + ), + } + ), ( enums::Connector::Deutschebank, RequiredFieldFinal { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 2fecba27bab..f0f44fc207d 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -98,6 +98,7 @@ coingate.base_url = "https://api-sandbox.coingate.com/v2" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" +datatrans.secondary_base_url = "https://pay.sandbox.datatrans.com/" deutschebank.base_url = "https://testmerch.directpos.de/rest-api" digitalvirgo.base_url = "https://dcb-integration-service-sandbox-external.staging.digitalvirgo.pl" dlocal.base_url = "https://sandbox.dlocal.com/" @@ -336,8 +337,8 @@ bank_debit.ach = { connector_list = "gocardless,adyen,stripe" } bank_debit.becs = { connector_list = "gocardless,stripe,adyen" } bank_debit.bacs = { connector_list = "stripe,gocardless" } bank_debit.sepa = { connector_list = "gocardless,adyen,stripe,deutschebank" } -card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" -card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.credit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" +card.debit.connector_list = "stripe,adyen,authorizedotnet,cybersource,datatrans,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,nuvei,payme,wellsfargo,bamboraapac,elavon,fiuu,nexixpay,novalnet,paybox,paypal" pay_later.klarna.connector_list = "adyen" wallet.apple_pay.connector_list = "stripe,adyen,cybersource,noon,bankofamerica,nexinets,novalnet" wallet.samsung_pay.connector_list = "cybersource" From 9772cb36ee038023bee6d970d424e494b2e7aef6 Mon Sep 17 00:00:00 2001 From: likhinbopanna <131246334+likhinbopanna@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:24:23 +0530 Subject: [PATCH 064/133] ci(cypress): Add Tests for Customer Deletion and Psync flows (#7158) --- .../00026-DeletedCustomerPsyncFlow.cy.js | 337 ++++++++++++++++++ ....js => 00027-BusinessProfileConfigs.cy.js} | 0 ...Fields.cy.js => 00028-DynamicFields.cy.js} | 0 ...Auth.cy.js => 00029-IncrementalAuth.cy.js} | 0 cypress-tests/cypress/support/commands.js | 98 ++--- 5 files changed, 392 insertions(+), 43 deletions(-) create mode 100644 cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js rename cypress-tests/cypress/e2e/spec/Payment/{00026-BusinessProfileConfigs.cy.js => 00027-BusinessProfileConfigs.cy.js} (100%) rename cypress-tests/cypress/e2e/spec/Payment/{00027-DynamicFields.cy.js => 00028-DynamicFields.cy.js} (100%) rename cypress-tests/cypress/e2e/spec/Payment/{00028-IncrementalAuth.cy.js => 00029-IncrementalAuth.cy.js} (100%) diff --git a/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js new file mode 100644 index 00000000000..365137ee34b --- /dev/null +++ b/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js @@ -0,0 +1,337 @@ +import * as fixtures from "../../../fixtures/imports"; +import State from "../../../utils/State"; +import getConnectorDetails, * as utils from "../../configs/Payment/Utils"; + +let globalState; + +describe("Card - Customer Deletion and Psync", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + context("Card - Psync after Customer Deletion for Automatic Capture", () => { + context("No3DS Card - Psync after Customer Deletion ", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Delete Customer", () => { + cy.customerDeleteCall(globalState); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("3DS Card - Psync after Customer Deletion ", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "automatic", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSAutoCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Delete Customer", () => { + cy.customerDeleteCall(globalState); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + }); + + context("Card - Psync after Customer Deletion for Manual Capture", () => { + context("No3DS Card - Psync after Customer Deletion ", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "no_three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Capture Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Delete Customer", () => { + cy.customerDeleteCall(globalState); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + + context("3DS Card - Psync after Customer Deletion ", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + afterEach("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create Customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create Payment Intent", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; + + cy.createPaymentIntentTest( + fixtures.createPaymentBody, + data, + "three_ds", + "manual", + globalState + ); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Confirm Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Handle redirection", () => { + const expected_redirection = fixtures.confirmBody["return_url"]; + cy.handleRedirection(globalState, expected_redirection); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Capture Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + + if (shouldContinue) + shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["Capture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Delete Customer", () => { + cy.customerDeleteCall(globalState); + }); + + it("Retrieve Payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["3DSManualCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + }); + }); +}); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00026-BusinessProfileConfigs.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00027-BusinessProfileConfigs.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/spec/Payment/00026-BusinessProfileConfigs.cy.js rename to cypress-tests/cypress/e2e/spec/Payment/00027-BusinessProfileConfigs.cy.js diff --git a/cypress-tests/cypress/e2e/spec/Payment/00027-DynamicFields.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00028-DynamicFields.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/spec/Payment/00027-DynamicFields.cy.js rename to cypress-tests/cypress/e2e/spec/Payment/00028-DynamicFields.cy.js diff --git a/cypress-tests/cypress/e2e/spec/Payment/00028-IncrementalAuth.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js similarity index 100% rename from cypress-tests/cypress/e2e/spec/Payment/00028-IncrementalAuth.cy.js rename to cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 45be7043116..f04d7762fa0 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -2263,54 +2263,66 @@ Cypress.Commands.add( logRequestId(response.headers["x-request-id"]); cy.wrap(response).then(() => { - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.payment_id).to.equal(payment_id); - expect(response.body.amount).to.equal(globalState.get("paymentAmount")); - expect(response.body.profile_id, "profile_id").to.not.be.null; - expect(response.body.billing, "billing_address").to.not.be.empty; - expect(response.body.customer, "customer").to.not.be.empty; - if ( - ["succeeded", "processing", "requires_customer_action"].includes( - response.body.status - ) - ) { - expect(response.body.connector, "connector").to.equal( - globalState.get("connectorId") + if (response.status === 200) { + expect(response.headers["content-type"]).to.include( + "application/json" ); - expect(response.body.payment_method_data, "payment_method_data").to - .not.be.empty; - expect(response.body.payment_method, "payment_method").to.not.be.null; - expect(response.body.merchant_connector_id, "connector_id").to.equal( - merchant_connector_id + expect(response.body.payment_id).to.equal(payment_id); + expect(response.body.amount).to.equal( + globalState.get("paymentAmount") ); - } + expect(response.body.profile_id, "profile_id").to.not.be.null; + expect(response.body.billing, "billing_address").to.not.be.empty; + expect(response.body.customer, "customer").to.not.be.empty; + if ( + ["succeeded", "processing", "requires_customer_action"].includes( + response.body.status + ) + ) { + expect(response.body.connector, "connector").to.equal( + globalState.get("connectorId") + ); + expect(response.body.payment_method_data, "payment_method_data").to + .not.be.empty; + expect(response.body.payment_method, "payment_method").to.not.be + .null; + expect( + response.body.merchant_connector_id, + "connector_id" + ).to.equal(merchant_connector_id); + } - if (autoretries) { - expect(response.body).to.have.property("attempts"); - expect(response.body.attempts).to.be.an("array").and.not.empty; - expect(response.body.attempts.length).to.equal(attempt); - expect(response.body.attempts[0].attempt_id).to.include( - `${payment_id}_` - ); - for (const key in response.body.attempts) { - if ( - response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && - response.body.status === "succeeded" - ) { - expect(response.body.attempts[key].status).to.equal("charged"); - } else if ( - response.body.attempts[key].attempt_id === - `${payment_id}_${attempt}` && - response.body.status === "requires_customer_action" - ) { - expect(response.body.attempts[key].status).to.equal( - "authentication_pending" - ); - } else { - expect(response.body.attempts[key].status).to.equal("failure"); + if (autoretries) { + expect(response.body).to.have.property("attempts"); + expect(response.body.attempts).to.be.an("array").and.not.empty; + expect(response.body.attempts.length).to.equal(attempt); + expect(response.body.attempts[0].attempt_id).to.include( + `${payment_id}_` + ); + for (const key in response.body.attempts) { + if ( + response.body.attempts[key].attempt_id === + `${payment_id}_${attempt}` && + response.body.status === "succeeded" + ) { + expect(response.body.attempts[key].status).to.equal("charged"); + } else if ( + response.body.attempts[key].attempt_id === + `${payment_id}_${attempt}` && + response.body.status === "requires_customer_action" + ) { + expect(response.body.attempts[key].status).to.equal( + "authentication_pending" + ); + } else { + expect(response.body.attempts[key].status).to.equal("failure"); + } } } + } else { + throw new Error( + `Retrieve Payment Call Failed with error code "${response.body.error.code}" error message "${response.body.error.message}"` + ); } }); }); From 50784ad1c13f0aa66a1da566ddd25e2621021538 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Fri, 7 Feb 2025 12:49:46 +0530 Subject: [PATCH 065/133] refactor(router): add display_name field to connector feature api (#7121) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 8 ++++++-- api-reference/openapi_spec.json | 8 ++++++-- crates/api_models/src/feature_matrix.rs | 3 ++- crates/hyperswitch_connectors/src/connectors/bambora.rs | 6 +++--- .../hyperswitch_connectors/src/connectors/deutschebank.rs | 8 ++++---- crates/hyperswitch_connectors/src/connectors/zsl.rs | 4 ++-- .../src/router_response_types.rs | 5 +++-- crates/router/src/routes/feature_matrix.rs | 3 ++- 8 files changed, 28 insertions(+), 17 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index ebfbfc3bf06..329bab6f3dd 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6471,14 +6471,14 @@ "type": "object", "required": [ "three_ds", - "non_three_ds", + "no_three_ds", "supported_card_networks" ], "properties": { "three_ds": { "$ref": "#/components/schemas/FeatureStatus" }, - "non_three_ds": { + "no_three_ds": { "$ref": "#/components/schemas/FeatureStatus" }, "supported_card_networks": { @@ -6776,6 +6776,10 @@ "name": { "type": "string" }, + "display_name": { + "type": "string", + "nullable": true + }, "description": { "type": "string", "nullable": true diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 7132fe905c7..d1bfc5dfdba 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9228,14 +9228,14 @@ "type": "object", "required": [ "three_ds", - "non_three_ds", + "no_three_ds", "supported_card_networks" ], "properties": { "three_ds": { "$ref": "#/components/schemas/FeatureStatus" }, - "non_three_ds": { + "no_three_ds": { "$ref": "#/components/schemas/FeatureStatus" }, "supported_card_networks": { @@ -9526,6 +9526,10 @@ "name": { "type": "string" }, + "display_name": { + "type": "string", + "nullable": true + }, "description": { "type": "string", "nullable": true diff --git a/crates/api_models/src/feature_matrix.rs b/crates/api_models/src/feature_matrix.rs index c1eb699705f..caa5aed1114 100644 --- a/crates/api_models/src/feature_matrix.rs +++ b/crates/api_models/src/feature_matrix.rs @@ -19,7 +19,7 @@ pub struct CardSpecificFeatures { /// Indicates whether three_ds card payments are supported. pub three_ds: FeatureStatus, /// Indicates whether non three_ds card payments are supported. - pub non_three_ds: FeatureStatus, + pub no_three_ds: FeatureStatus, /// List of supported card networks pub supported_card_networks: Vec, } @@ -47,6 +47,7 @@ pub struct SupportedPaymentMethod { #[derive(Debug, ToSchema, Serialize)] pub struct ConnectorFeatureMatrixResponse { pub name: String, + pub display_name: Option, pub description: Option, pub category: Option, pub supported_payment_methods: Vec, diff --git a/crates/hyperswitch_connectors/src/connectors/bambora.rs b/crates/hyperswitch_connectors/src/connectors/bambora.rs index d972e004f2d..c4a9edea65c 100644 --- a/crates/hyperswitch_connectors/src/connectors/bambora.rs +++ b/crates/hyperswitch_connectors/src/connectors/bambora.rs @@ -847,7 +847,7 @@ lazy_static! { api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ api_models::feature_matrix::CardSpecificFeatures { three_ds: common_enums::FeatureStatus::Supported, - non_three_ds: common_enums::FeatureStatus::Supported, + no_three_ds: common_enums::FeatureStatus::Supported, supported_card_networks: supported_card_network.clone(), } }), @@ -858,8 +858,8 @@ lazy_static! { bambora_supported_payment_methods }; static ref BAMBORA_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { - description: "Bambora is a leading online payment provider in Canada and United States." - .to_string(), + display_name: "Bambora", + description: "Bambora is a leading online payment provider in Canada and United States.", connector_type: enums::PaymentConnectorCategory::PaymentGateway, }; static ref BAMBORA_SUPPORTED_WEBHOOK_FLOWS: Vec = Vec::new(); diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 0b6b2f132e7..0b92cb2538c 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -1031,7 +1031,7 @@ lazy_static! { api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ api_models::feature_matrix::CardSpecificFeatures { three_ds: common_enums::FeatureStatus::Supported, - non_three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::NotSupported, supported_card_networks: supported_card_network.clone(), } }), @@ -1050,7 +1050,7 @@ lazy_static! { api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ api_models::feature_matrix::CardSpecificFeatures { three_ds: common_enums::FeatureStatus::Supported, - non_three_ds: common_enums::FeatureStatus::NotSupported, + no_three_ds: common_enums::FeatureStatus::NotSupported, supported_card_networks: supported_card_network.clone(), } }), @@ -1062,9 +1062,9 @@ lazy_static! { }; static ref DEUTSCHEBANK_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Deutsche Bank", description: - "Deutsche Bank is a German multinational investment bank and financial services company " - .to_string(), + "Deutsche Bank is a German multinational investment bank and financial services company ", connector_type: enums::PaymentConnectorCategory::BankAcquirer, }; diff --git a/crates/hyperswitch_connectors/src/connectors/zsl.rs b/crates/hyperswitch_connectors/src/connectors/zsl.rs index cf01c31b747..d7c0152b7a6 100644 --- a/crates/hyperswitch_connectors/src/connectors/zsl.rs +++ b/crates/hyperswitch_connectors/src/connectors/zsl.rs @@ -466,9 +466,9 @@ lazy_static! { }; static ref ZSL_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "ZSL", description: - "Zsl is a payment gateway operating in China, specializing in facilitating local bank transfers" - .to_string(), + "Zsl is a payment gateway operating in China, specializing in facilitating local bank transfers", connector_type: enums::PaymentConnectorCategory::PaymentGateway, }; diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 4d15d715ff7..5578647025b 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -569,9 +569,10 @@ pub type SupportedPaymentMethods = HashMap Date: Fri, 7 Feb 2025 15:18:24 +0530 Subject: [PATCH 066/133] refactor(connector): Move connectors Aci, Braintree, Globalpay, Iatapay, Itaubank, Klarna, Mifinity and Nuvei from router to hyperswitch_connectors crate (#7167) Co-authored-by: Spriti Aneja Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- Cargo.lock | 1 + crates/hyperswitch_connectors/Cargo.toml | 1 + .../hyperswitch_connectors/src/connectors.rs | 37 +- .../src/connectors}/aci.rs | 361 ++++----- .../src/connectors/aci/aci_result_codes.rs} | 0 .../src/connectors}/aci/transformers.rs | 309 ++++---- .../src/connectors}/braintree.rs | 605 +++++++-------- .../src/connectors}/braintree/transformers.rs | 364 +++++---- .../src/connectors}/globalpay.rs | 566 +++++++------- .../src/connectors}/globalpay/requests.rs | 0 .../src/connectors}/globalpay/response.rs | 0 .../src/connectors}/globalpay/transformers.rs | 235 +++--- .../src/connectors}/iatapay.rs | 314 ++++---- .../src/connectors}/iatapay/transformers.rs | 289 ++++--- .../src/connectors}/itaubank.rs | 328 ++++---- .../src/connectors}/itaubank/transformers.rs | 163 ++-- .../src/connectors}/klarna.rs | 438 +++++------ .../src/connectors}/klarna/transformers.rs | 184 +++-- .../src/connectors}/mifinity.rs | 232 +++--- .../src/connectors}/mifinity/transformers.rs | 158 ++-- .../src/connectors}/nuvei.rs | 446 ++++++----- .../src/connectors}/nuvei/transformers.rs | 705 +++++++++--------- .../hyperswitch_connectors/src/constants.rs | 2 + .../src/default_implementations.rs | 274 ++++++- .../src/default_implementations_v2.rs | 175 +++++ crates/hyperswitch_connectors/src/types.rs | 9 +- crates/hyperswitch_connectors/src/utils.rs | 94 ++- crates/router/src/connector.rs | 55 +- crates/router/src/consts.rs | 1 - .../connector_integration_v2_impls.rs | 186 ----- crates/router/src/core/payments/flows.rs | 272 ------- 31 files changed, 3339 insertions(+), 3465 deletions(-) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/aci.rs (59%) rename crates/{router/src/connector/aci/result_codes.rs => hyperswitch_connectors/src/connectors/aci/aci_result_codes.rs} (100%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/aci/transformers.rs (72%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/braintree.rs (66%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/braintree/transformers.rs (84%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/globalpay.rs (61%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/globalpay/requests.rs (100%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/globalpay/response.rs (100%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/globalpay/transformers.rs (65%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/iatapay.rs (71%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/iatapay/transformers.rs (68%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/itaubank.rs (71%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/itaubank/transformers.rs (71%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/klarna.rs (78%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/klarna/transformers.rs (81%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/mifinity.rs (68%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/mifinity/transformers.rs (72%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/nuvei.rs (67%) rename crates/{router/src/connector => hyperswitch_connectors/src/connectors}/nuvei/transformers.rs (72%) diff --git a/Cargo.lock b/Cargo.lock index 9e39e50ff52..5b4d9767029 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4031,6 +4031,7 @@ dependencies = [ "serde_qs", "serde_urlencoded", "serde_with", + "sha1", "strum 0.26.3", "time", "url", diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 68ea60af8c6..6b76e6ac195 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -36,6 +36,7 @@ serde_json = "1.0.115" serde_qs = "0.12.0" serde_urlencoded = "0.7.1" serde_with = "3.7.0" +sha1 = { version = "0.10.6" } strum = { version = "0.26", features = ["derive"] } time = "0.3.35" url = "2.5.0" diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index e9738a4907a..58e6d9c7e5d 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -1,3 +1,4 @@ +pub mod aci; pub mod airwallex; pub mod amazonpay; pub mod bambora; @@ -7,6 +8,7 @@ pub mod billwerk; pub mod bitpay; pub mod bluesnap; pub mod boku; +pub mod braintree; pub mod cashtocode; pub mod chargebee; pub mod coinbase; @@ -23,17 +25,23 @@ pub mod fiserv; pub mod fiservemea; pub mod fiuu; pub mod forte; +pub mod globalpay; pub mod globepay; pub mod gocardless; pub mod helcim; +pub mod iatapay; pub mod inespay; +pub mod itaubank; pub mod jpmorgan; +pub mod klarna; +pub mod mifinity; pub mod mollie; pub mod multisafepay; pub mod nexinets; pub mod nexixpay; pub mod nomupay; pub mod novalnet; +pub mod nuvei; pub mod paybox; pub mod payeezy; pub mod payu; @@ -59,19 +67,20 @@ pub mod zen; pub mod zsl; pub use self::{ - airwallex::Airwallex, amazonpay::Amazonpay, bambora::Bambora, bamboraapac::Bamboraapac, - bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, bluesnap::Bluesnap, - boku::Boku, cashtocode::Cashtocode, chargebee::Chargebee, coinbase::Coinbase, - coingate::Coingate, cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, - cybersource::Cybersource, datatrans::Datatrans, deutschebank::Deutschebank, - digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, fiserv::Fiserv, - fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globepay::Globepay, gocardless::Gocardless, - helcim::Helcim, inespay::Inespay, jpmorgan::Jpmorgan, mollie::Mollie, + aci::Aci, airwallex::Airwallex, amazonpay::Amazonpay, bambora::Bambora, + bamboraapac::Bamboraapac, bankofamerica::Bankofamerica, billwerk::Billwerk, bitpay::Bitpay, + bluesnap::Bluesnap, boku::Boku, braintree::Braintree, cashtocode::Cashtocode, + chargebee::Chargebee, coinbase::Coinbase, coingate::Coingate, cryptopay::Cryptopay, + ctp_mastercard::CtpMastercard, cybersource::Cybersource, datatrans::Datatrans, + deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, + fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globalpay::Globalpay, + globepay::Globepay, gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, inespay::Inespay, + itaubank::Itaubank, jpmorgan::Jpmorgan, klarna::Klarna, mifinity::Mifinity, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, - novalnet::Novalnet, paybox::Paybox, payeezy::Payeezy, payu::Payu, placetopay::Placetopay, - powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, - redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, - tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, volt::Volt, - wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, - zsl::Zsl, + novalnet::Novalnet, nuvei::Nuvei, paybox::Paybox, payeezy::Payeezy, payu::Payu, + placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, + razorpay::Razorpay, redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, + thunes::Thunes, tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, + volt::Volt, wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, + zen::Zen, zsl::Zsl, }; diff --git a/crates/router/src/connector/aci.rs b/crates/hyperswitch_connectors/src/connectors/aci.rs similarity index 59% rename from crates/router/src/connector/aci.rs rename to crates/hyperswitch_connectors/src/connectors/aci.rs index c59c0ce0755..f4fce668ee0 100644 --- a/crates/router/src/connector/aci.rs +++ b/crates/hyperswitch_connectors/src/connectors/aci.rs @@ -1,32 +1,55 @@ -mod result_codes; +mod aci_result_codes; pub mod transformers; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::enums; use common_utils::{ - request::RequestContent, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; -use hyperswitch_interfaces::api::ConnectorSpecifications; -use masking::PeekInterface; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsSyncRouterData, + RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + PaymentsAuthorizeType, PaymentsSyncType, PaymentsVoidType, RefundExecuteType, Response, + }, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; use transformers as aci; -use super::utils::{convert_amount, is_mandate_supported, PaymentsAuthorizeRequestData}; use crate::{ - configs::settings, - connector::utils::PaymentMethodDataType, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorValidation, + constants::headers, + types::ResponseRouterData, + utils::{ + convert_amount, is_mandate_supported, PaymentMethodDataType, PaymentsAuthorizeRequestData, }, - types::{ - self, - api::{self, ConnectorCommon}, - }, - utils::BytesExt, }; #[derive(Clone)] @@ -53,14 +76,14 @@ impl ConnectorCommon for Aci { "application/x-www-form-urlencoded" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.aci.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = aci::AciAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -71,16 +94,16 @@ impl ConnectorCommon for Aci { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { let response: aci::AciErrorResponse = res .response .parse_struct("AciErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; event_builder.map(|i| i.set_error_response_body(&response)); router_env::logger::info!(connector_response=?response); - Ok(types::ErrorResponse { + Ok(ErrorResponse { status_code: res.status_code, code: response.result.code, message: response.result.description, @@ -105,8 +128,8 @@ impl ConnectorCommon for Aci { impl ConnectorValidation for Aci { fn validate_mandate_payment( &self, - pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_type: Option, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) @@ -123,83 +146,46 @@ impl api::PaymentSession for Aci {} impl api::ConnectorAccessToken for Aci {} impl api::PaymentToken for Aci {} -impl - services::ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Aci +impl ConnectorIntegration + for Aci { // Not Implemented (R) } -impl - services::ConnectorIntegration< - api::Session, - types::PaymentsSessionData, - types::PaymentsResponseData, - > for Aci -{ +impl ConnectorIntegration for Aci { // Not Implemented (R) } -impl - services::ConnectorIntegration< - api::AccessTokenAuth, - types::AccessTokenRequestData, - types::AccessToken, - > for Aci -{ +impl ConnectorIntegration for Aci { // Not Implemented (R) } impl api::MandateSetup for Aci {} -impl - services::ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Aci -{ +impl ConnectorIntegration for Aci { // Issue: #173 fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::NotImplemented("Setup Mandate flow for Aci".to_string()).into()) } } -impl - services::ConnectorIntegration< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - > for Aci -{ +impl ConnectorIntegration for Aci { // Not Implemented (R) } -impl - services::ConnectorIntegration - for Aci -{ +impl ConnectorIntegration for Aci { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsSyncType::get_content_type(self) - .to_string() - .into(), + PaymentsSyncType::get_content_type(self).to_string().into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); @@ -212,8 +198,8 @@ impl fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let auth = aci::AciAuthType::try_from(&req.connector_auth_type)?; Ok(format!( @@ -231,28 +217,28 @@ impl fn build_request( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult + res: Response, + ) -> CustomResult where - types::PaymentsSyncData: Clone, - types::PaymentsResponseData: Clone, + PaymentsSyncData: Clone, + PaymentsResponseData: Clone, { let response: aci::AciPaymentsResponse = res.response @@ -260,7 +246,7 @@ impl .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -270,28 +256,22 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl - services::ConnectorIntegration< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > for Aci -{ +impl ConnectorIntegration for Aci { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self) + PaymentsAuthorizeType::get_content_type(self) .to_string() .into(), )]; @@ -306,8 +286,8 @@ impl fn get_url( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { match req.request.connector_mandate_id() { Some(mandate_id) => Ok(format!( @@ -321,8 +301,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { // encode only for for urlencoded things. @@ -340,24 +320,16 @@ impl fn build_request( &self, - req: &types::RouterData< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -366,17 +338,17 @@ impl fn handle_response( &self, - data: &types::PaymentsAuthorizeRouterData, + data: &PaymentsAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: aci::AciPaymentsResponse = res.response .parse_struct("AciPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -386,28 +358,22 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl - services::ConnectorIntegration< - api::Void, - types::PaymentsCancelData, - types::PaymentsResponseData, - > for Aci -{ +impl ConnectorIntegration for Aci { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self) + PaymentsAuthorizeType::get_content_type(self) .to_string() .into(), )]; @@ -422,8 +388,8 @@ impl fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { let id = &req.request.connector_transaction_id; Ok(format!("{}v1/payments/{}", self.base_url(connectors), id)) @@ -431,43 +397,41 @@ impl fn get_request_body( &self, - req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = aci::AciCancelRequest::try_from(req)?; Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsVoidType::get_request_body( - self, req, connectors, - )?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsCancelRouterData, + data: &PaymentsCancelRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: aci::AciPaymentsResponse = res.response .parse_struct("AciPaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -477,9 +441,9 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } @@ -488,19 +452,15 @@ impl api::Refund for Aci {} impl api::RefundExecute for Aci {} impl api::RefundSync for Aci {} -impl services::ConnectorIntegration - for Aci -{ +impl ConnectorIntegration for Aci { fn get_headers( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::RefundExecuteType::get_content_type(self) - .to_string() - .into(), + RefundExecuteType::get_content_type(self).to_string().into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); @@ -513,8 +473,8 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let connector_payment_id = req.request.connector_transaction_id.clone(); Ok(format!( @@ -526,8 +486,8 @@ impl services::ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let amount = convert_amount( self.amount_converter, @@ -542,30 +502,26 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: aci::AciRefundResponse = res .response .parse_struct("AciRefundResponse") @@ -573,7 +529,7 @@ impl services::ConnectorIntegration, - ) -> CustomResult { + ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl services::ConnectorIntegration - for Aci -{ -} +impl ConnectorIntegration for Aci {} #[async_trait::async_trait] -impl api::IncomingWebhook for Aci { +impl IncomingWebhook for Aci { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/aci/result_codes.rs b/crates/hyperswitch_connectors/src/connectors/aci/aci_result_codes.rs similarity index 100% rename from crates/router/src/connector/aci/result_codes.rs rename to crates/hyperswitch_connectors/src/connectors/aci/aci_result_codes.rs diff --git a/crates/router/src/connector/aci/transformers.rs b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs similarity index 72% rename from crates/router/src/connector/aci/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/aci/transformers.rs index 11d34e55b7b..3246f7d01d5 100644 --- a/crates/router/src/connector/aci/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs @@ -1,17 +1,26 @@ use std::str::FromStr; -use common_utils::{id_type, pii::Email, types::StringMajorUnit}; +use common_enums::enums; +use common_utils::{id_type, pii::Email, request::Method, types::StringMajorUnit}; use error_stack::report; +use hyperswitch_domain_models::{ + payment_method_data::{BankRedirectData, Card, PayLaterData, PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, RouterData}, + router_request_types::ResponseId, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{PaymentsAuthorizeRouterData, PaymentsCancelRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; use masking::{ExposeInterface, Secret}; -use reqwest::Url; use serde::{Deserialize, Serialize}; +use url::Url; -use super::result_codes::{FAILURE_CODES, PENDING_CODES, SUCCESSFUL_CODES}; +use super::aci_result_codes::{FAILURE_CODES, PENDING_CODES, SUCCESSFUL_CODES}; use crate::{ - connector::utils::{self, PhoneDetailsData, RouterData}, - core::errors, - services, - types::{self, domain, storage::enums}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, PhoneDetailsData, RouterData as _}, }; type Error = error_stack::Report; @@ -36,10 +45,10 @@ pub struct AciAuthType { pub entity_id: Secret, } -impl TryFrom<&types::ConnectorAuthType> for AciAuthType { +impl TryFrom<&ConnectorAuthType> for AciAuthType { type Error = error_stack::Report; - fn try_from(item: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = item { + fn try_from(item: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = item { Ok(Self { api_key: api_key.to_owned(), entity_id: key1.to_owned(), @@ -89,51 +98,49 @@ pub enum PaymentDetails { Mandate, } -impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> for PaymentDetails { +impl TryFrom<(&WalletData, &PaymentsAuthorizeRouterData)> for PaymentDetails { type Error = Error; - fn try_from( - value: (&domain::WalletData, &types::PaymentsAuthorizeRouterData), - ) -> Result { + fn try_from(value: (&WalletData, &PaymentsAuthorizeRouterData)) -> Result { let (wallet_data, item) = value; let payment_data = match wallet_data { - domain::WalletData::MbWayRedirect(_) => { + WalletData::MbWayRedirect(_) => { let phone_details = item.get_billing_phone()?; Self::Wallet(Box::new(WalletPMData { payment_brand: PaymentBrand::Mbway, account_id: Some(phone_details.get_number_with_hash_country_code()?), })) } - domain::WalletData::AliPayRedirect { .. } => Self::Wallet(Box::new(WalletPMData { + WalletData::AliPayRedirect { .. } => Self::Wallet(Box::new(WalletPMData { payment_brand: PaymentBrand::AliPay, account_id: None, })), - domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::AmazonPayRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect { .. } - | domain::WalletData::GooglePay(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::Paze(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect { .. } - | domain::WalletData::VippsRedirect { .. } - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::AliPayQr(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect { .. } + | WalletData::GooglePay(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect { .. } + | WalletData::VippsRedirect { .. } + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::AliPayQr(_) + | WalletData::ApplePayRedirect(_) + | WalletData::GooglePayRedirect(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( "Payment method".to_string(), ))?, }; @@ -143,33 +150,31 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> for Pay impl TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::BankRedirectData, + &AciRouterData<&PaymentsAuthorizeRouterData>, + &BankRedirectData, )> for PaymentDetails { type Error = Error; fn try_from( value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::BankRedirectData, + &AciRouterData<&PaymentsAuthorizeRouterData>, + &BankRedirectData, ), ) -> Result { let (item, bank_redirect_data) = value; let payment_data = match bank_redirect_data { - domain::BankRedirectData::Eps { .. } => { - Self::BankRedirect(Box::new(BankRedirectionPMData { - payment_brand: PaymentBrand::Eps, - bank_account_country: Some(item.router_data.get_billing_country()?), - bank_account_bank_name: None, - bank_account_bic: None, - bank_account_iban: None, - billing_country: None, - merchant_customer_id: None, - merchant_transaction_id: None, - customer_email: None, - })) - } - domain::BankRedirectData::Giropay { + BankRedirectData::Eps { .. } => Self::BankRedirect(Box::new(BankRedirectionPMData { + payment_brand: PaymentBrand::Eps, + bank_account_country: Some(item.router_data.get_billing_country()?), + bank_account_bank_name: None, + bank_account_bic: None, + bank_account_iban: None, + billing_country: None, + merchant_customer_id: None, + merchant_transaction_id: None, + customer_email: None, + })), + BankRedirectData::Giropay { bank_account_bic, bank_account_iban, .. @@ -184,7 +189,7 @@ impl merchant_transaction_id: None, customer_email: None, })), - domain::BankRedirectData::Ideal { bank_name, .. } => { + BankRedirectData::Ideal { bank_name, .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Ideal, bank_account_country: Some(item.router_data.get_billing_country()?), @@ -201,7 +206,7 @@ impl customer_email: None, })) } - domain::BankRedirectData::Sofort { .. } => { + BankRedirectData::Sofort { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Sofortueberweisung, bank_account_country: Some(item.router_data.get_billing_country()?), @@ -214,7 +219,7 @@ impl customer_email: None, })) } - domain::BankRedirectData::Przelewy24 { .. } => { + BankRedirectData::Przelewy24 { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Przelewy, bank_account_country: None, @@ -227,7 +232,7 @@ impl customer_email: Some(item.router_data.get_billing_email()?), })) } - domain::BankRedirectData::Interac { .. } => { + BankRedirectData::Interac { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::InteracOnline, bank_account_country: Some(item.router_data.get_billing_country()?), @@ -240,7 +245,7 @@ impl customer_email: Some(item.router_data.get_billing_email()?), })) } - domain::BankRedirectData::Trustly { .. } => { + BankRedirectData::Trustly { .. } => { Self::BankRedirect(Box::new(BankRedirectionPMData { payment_brand: PaymentBrand::Trustly, bank_account_country: None, @@ -255,17 +260,17 @@ impl customer_email: None, })) } - domain::BankRedirectData::Bizum { .. } - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::LocalBankRedirect {} - | domain::BankRedirectData::OpenBankingUk { .. } => Err( + BankRedirectData::Bizum { .. } + | BankRedirectData::Blik { .. } + | BankRedirectData::BancontactCard { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} + | BankRedirectData::OpenBankingUk { .. } => Err( errors::ConnectorError::NotImplemented("Payment method".to_string()), )?, }; @@ -273,10 +278,10 @@ impl } } -impl TryFrom<(domain::payments::Card, Option>)> for PaymentDetails { +impl TryFrom<(Card, Option>)> for PaymentDetails { type Error = Error; fn try_from( - (card_data, card_holder_name): (domain::payments::Card, Option>), + (card_data, card_holder_name): (Card, Option>), ) -> Result { Ok(Self::AciCard(Box::new(CardDetails { card_number: card_data.card_number, @@ -406,23 +411,19 @@ pub enum AciPaymentType { Refund, } -impl TryFrom<&AciRouterData<&types::PaymentsAuthorizeRouterData>> for AciPaymentsRequest { +impl TryFrom<&AciRouterData<&PaymentsAuthorizeRouterData>> for AciPaymentsRequest { type Error = error_stack::Report; - fn try_from( - item: &AciRouterData<&types::PaymentsAuthorizeRouterData>, - ) -> Result { + fn try_from(item: &AciRouterData<&PaymentsAuthorizeRouterData>) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(ref card_data) => Self::try_from((item, card_data)), - domain::PaymentMethodData::Wallet(ref wallet_data) => { - Self::try_from((item, wallet_data)) - } - domain::PaymentMethodData::PayLater(ref pay_later_data) => { + PaymentMethodData::Card(ref card_data) => Self::try_from((item, card_data)), + PaymentMethodData::Wallet(ref wallet_data) => Self::try_from((item, wallet_data)), + PaymentMethodData::PayLater(ref pay_later_data) => { Self::try_from((item, pay_later_data)) } - domain::PaymentMethodData::BankRedirect(ref bank_redirect_data) => { + PaymentMethodData::BankRedirect(ref bank_redirect_data) => { Self::try_from((item, bank_redirect_data)) } - domain::PaymentMethodData::MandatePayment => { + PaymentMethodData::MandatePayment => { let mandate_id = item.router_data.request.mandate_id.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "mandate_id", @@ -430,20 +431,20 @@ impl TryFrom<&AciRouterData<&types::PaymentsAuthorizeRouterData>> for AciPayment )?; Self::try_from((item, mandate_id)) } - domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::Crypto(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Aci"), ))? @@ -452,18 +453,10 @@ impl TryFrom<&AciRouterData<&types::PaymentsAuthorizeRouterData>> for AciPayment } } -impl - TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, - )> for AciPaymentsRequest -{ +impl TryFrom<(&AciRouterData<&PaymentsAuthorizeRouterData>, &WalletData)> for AciPaymentsRequest { type Error = Error; fn try_from( - value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::WalletData, - ), + value: (&AciRouterData<&PaymentsAuthorizeRouterData>, &WalletData), ) -> Result { let (item, wallet_data) = value; let txn_details = get_transaction_details(item)?; @@ -480,15 +473,15 @@ impl impl TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::BankRedirectData, + &AciRouterData<&PaymentsAuthorizeRouterData>, + &BankRedirectData, )> for AciPaymentsRequest { type Error = Error; fn try_from( value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::BankRedirectData, + &AciRouterData<&PaymentsAuthorizeRouterData>, + &BankRedirectData, ), ) -> Result { let (item, bank_redirect_data) = value; @@ -504,18 +497,10 @@ impl } } -impl - TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::payments::PayLaterData, - )> for AciPaymentsRequest -{ +impl TryFrom<(&AciRouterData<&PaymentsAuthorizeRouterData>, &PayLaterData)> for AciPaymentsRequest { type Error = Error; fn try_from( - value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::payments::PayLaterData, - ), + value: (&AciRouterData<&PaymentsAuthorizeRouterData>, &PayLaterData), ) -> Result { let (item, _pay_later_data) = value; let txn_details = get_transaction_details(item)?; @@ -530,18 +515,10 @@ impl } } -impl - TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::Card, - )> for AciPaymentsRequest -{ +impl TryFrom<(&AciRouterData<&PaymentsAuthorizeRouterData>, &Card)> for AciPaymentsRequest { type Error = Error; fn try_from( - value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, - &domain::Card, - ), + value: (&AciRouterData<&PaymentsAuthorizeRouterData>, &Card), ) -> Result { let (item, card_data) = value; let card_holder_name = item.router_data.get_optional_billing_full_name(); @@ -560,14 +537,14 @@ impl impl TryFrom<( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, + &AciRouterData<&PaymentsAuthorizeRouterData>, api_models::payments::MandateIds, )> for AciPaymentsRequest { type Error = Error; fn try_from( value: ( - &AciRouterData<&types::PaymentsAuthorizeRouterData>, + &AciRouterData<&PaymentsAuthorizeRouterData>, api_models::payments::MandateIds, ), ) -> Result { @@ -585,7 +562,7 @@ impl } fn get_transaction_details( - item: &AciRouterData<&types::PaymentsAuthorizeRouterData>, + item: &AciRouterData<&PaymentsAuthorizeRouterData>, ) -> Result> { let auth = AciAuthType::try_from(&item.router_data.connector_auth_type)?; Ok(TransactionDetails { @@ -597,7 +574,7 @@ fn get_transaction_details( } fn get_instruction_details( - item: &AciRouterData<&types::PaymentsAuthorizeRouterData>, + item: &AciRouterData<&PaymentsAuthorizeRouterData>, ) -> Option { if item.router_data.request.setup_mandate_details.is_some() { return Some(Instruction { @@ -617,9 +594,9 @@ fn get_instruction_details( None } -impl TryFrom<&types::PaymentsCancelRouterData> for AciCancelRequest { +impl TryFrom<&PaymentsCancelRouterData> for AciCancelRequest { type Error = error_stack::Report; - fn try_from(item: &types::PaymentsCancelRouterData) -> Result { + fn try_from(item: &PaymentsCancelRouterData) -> Result { let auth = AciAuthType::try_from(&item.connector_auth_type)?; let aci_payment_request = Self { entity_id: auth.entity_id, @@ -692,7 +669,7 @@ pub struct AciErrorResponse { #[derive(Clone, Debug, Deserialize, PartialEq, Eq, Serialize)] #[serde(rename_all = "camelCase")] pub struct AciRedirectionData { - method: Option, + method: Option, parameters: Vec, url: Url, } @@ -718,13 +695,12 @@ pub struct ErrorParameters { pub(super) message: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let redirection_data = item.response.redirect.map(|data| { let form_fields = std::collections::HashMap::<_, _>::from_iter( @@ -735,24 +711,21 @@ impl // If method is Get, parameters are appended to URL // If method is post, we http Post the method to URL - services::RedirectForm::Form { + RedirectForm::Form { endpoint: data.url.to_string(), // Handles method for Bank redirects currently. // 3DS response have method within preconditions. That would require replacing below line with a function. - method: data.method.unwrap_or(services::Method::Post), + method: data.method.unwrap_or(Method::Post), form_fields, } }); - let mandate_reference = item - .response - .registration_id - .map(|id| types::MandateReference { - connector_mandate_id: Some(id.expose()), - payment_method_id: None, - mandate_metadata: None, - connector_mandate_request_reference_id: None, - }); + let mandate_reference = item.response.registration_id.map(|id| MandateReference { + connector_mandate_id: Some(id.expose()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }); Ok(Self { status: { @@ -764,8 +737,8 @@ impl )?) } }, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(redirection_data), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -788,9 +761,9 @@ pub struct AciRefundRequest { pub entity_id: Secret, } -impl TryFrom<&AciRouterData<&types::RefundsRouterData>> for AciRefundRequest { +impl TryFrom<&AciRouterData<&RefundsRouterData>> for AciRefundRequest { type Error = error_stack::Report; - fn try_from(item: &AciRouterData<&types::RefundsRouterData>) -> Result { + fn try_from(item: &AciRouterData<&RefundsRouterData>) -> Result { let amount = item.amount.to_owned(); let currency = item.router_data.request.currency; let payment_type = AciPaymentType::Refund; @@ -852,15 +825,13 @@ pub struct AciRefundResponse { pub(super) result: ResultCode, } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, refund_status: enums::RefundStatus::from(AciRefundStatus::from_str( &item.response.result.code, diff --git a/crates/router/src/connector/braintree.rs b/crates/hyperswitch_connectors/src/connectors/braintree.rs similarity index 66% rename from crates/router/src/connector/braintree.rs rename to crates/hyperswitch_connectors/src/connectors/braintree.rs index 7a88180c64d..705bf0688fa 100644 --- a/crates/router/src/connector/braintree.rs +++ b/crates/hyperswitch_connectors/src/connectors/braintree.rs @@ -2,43 +2,67 @@ pub mod transformers; use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; +use common_enums::{enums, CallConnectorAction, PaymentAction}; use common_utils::{ + consts::BASE64_ENGINE, crypto, - ext_traits::XmlExt, - request::RequestContent, + errors::{CustomResult, ParsingError}, + ext_traits::{BytesExt, XmlExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; -use diesel_models::enums; use error_stack::{report, Report, ResultExt}; -use hyperswitch_interfaces::webhooks::IncomingWebhookFlowError; -use masking::{ExposeInterface, PeekInterface, Secret}; +use hyperswitch_domain_models::{ + api::ApplicationResponse, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, + }, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, TokenizationRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + disputes::DisputePayload, + errors, + events::connector_api_logs::ConnectorEvent, + types::{ + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsCompleteAuthorizeType, + PaymentsSyncType, PaymentsVoidType, RefundExecuteType, RefundSyncType, Response, + TokenizationType, + }, + webhooks::{IncomingWebhook, IncomingWebhookFlowError, IncomingWebhookRequestDetails}, +}; +use masking::{ExposeInterface, Mask, PeekInterface, Secret}; use ring::hmac; +use router_env::logger; use sha1::{Digest, Sha1}; +use transformers::{self as braintree, get_status}; -use self::transformers as braintree; -use super::utils::{self as connector_utils, PaymentsAuthorizeRequestData}; use crate::{ - configs::settings, - connector::utils::PaymentMethodDataType, - consts, - core::{ - errors::{self, CustomResult}, - payments, - }, - events::connector_api_logs::ConnectorEvent, - headers, logger, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, - }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - transformers::ForeignFrom, - ErrorResponse, + constants::headers, + types::ResponseRouterData, + utils::{ + self, convert_amount, is_mandate_supported, to_currency_lower_unit, PaymentMethodDataType, + PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, }, - utils::{self, BytesExt}, }; #[derive(Clone)] @@ -63,9 +87,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![ ( headers::CONTENT_TYPE.to_string(), @@ -91,18 +115,18 @@ impl ConnectorCommon for Braintree { api::CurrencyUnit::Base } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.braintree.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = braintree::BraintreeAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let auth_key = format!("{}:{}", auth.public_key.peek(), auth.private_key.peek()); - let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); + let auth_header = format!("Basic {}", BASE64_ENGINE.encode(auth_key)); Ok(vec![( headers::AUTHORIZATION.to_string(), auth_header.into_masked(), @@ -111,13 +135,11 @@ impl ConnectorCommon for Braintree { fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - let response: Result< - braintree::ErrorResponses, - Report, - > = res.response.parse_struct("Braintree Error Response"); + let response: Result> = + res.response.parse_struct("Braintree Error Response"); match response { Ok(braintree::ErrorResponses::BraintreeApiErrorResponse(response)) => { @@ -135,10 +157,7 @@ impl ConnectorCommon for Braintree { .and_then(|credit_card_error| credit_card_error.errors.first())) })); let (code, message) = error.map_or( - ( - consts::NO_ERROR_CODE.to_string(), - consts::NO_ERROR_MESSAGE.to_string(), - ), + (NO_ERROR_CODE.to_string(), NO_ERROR_MESSAGE.to_string()), |error| (error.code.clone(), error.message.clone()), ); Ok(ErrorResponse { @@ -156,8 +175,8 @@ impl ConnectorCommon for Braintree { Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), reason: Some(response.errors), attempt_status: None, connector_transaction_id: None, @@ -185,18 +204,18 @@ impl ConnectorValidation for Braintree { | enums::CaptureMethod::Manual | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + utils::construct_not_implemented_error_report(capture_method, self.id()), ), } } fn validate_mandate_payment( &self, - pm_type: Option, + pm_type: Option, pm_data: hyperswitch_domain_models::payment_method_data::PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } @@ -210,31 +229,22 @@ impl api::PaymentsCompleteAuthorize for Braintree {} impl api::PaymentSession for Braintree {} impl api::ConnectorAccessToken for Braintree {} -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { // Not Implemented (R) } -impl ConnectorIntegration - for Braintree -{ -} +impl ConnectorIntegration for Braintree {} impl api::PaymentToken for Braintree {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Braintree +impl ConnectorIntegration + for Braintree { fn get_headers( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -244,16 +254,16 @@ impl fn get_url( &self, - _req: &types::TokenizationRouterData, - connectors: &settings::Connectors, + _req: &TokenizationRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::TokenizationRouterData, - _connectors: &settings::Connectors, + req: &TokenizationRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = transformers::BraintreeTokenRequest::try_from(req)?; @@ -262,29 +272,27 @@ impl fn build_request( &self, - req: &types::TokenizationRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::TokenizationType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&TokenizationType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::TokenizationType::get_headers(self, req, connectors)?) - .set_body(types::TokenizationType::get_request_body( - self, req, connectors, - )?) + .headers(TokenizationType::get_headers(self, req, connectors)?) + .set_body(TokenizationType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::TokenizationRouterData, + data: &TokenizationRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult + res: Response, + ) -> CustomResult where - types::PaymentsResponseData: Clone, + PaymentsResponseData: Clone, { let response: transformers::BraintreeTokenResponse = res .response @@ -293,7 +301,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -301,7 +309,7 @@ impl } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -311,23 +319,15 @@ impl impl api::MandateSetup for Braintree {} #[allow(dead_code)] -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Braintree +impl ConnectorIntegration + for Braintree { // Not Implemented (R) fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Braintree".to_string()) .into(), @@ -335,14 +335,12 @@ impl } } -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -352,18 +350,18 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -377,18 +375,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -397,17 +393,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: transformers::BraintreeCaptureResponse = res .response .parse_struct("Braintree PaymentsCaptureResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -416,21 +412,19 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -440,16 +434,16 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::PaymentsSyncRouterData, - _connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = transformers::BraintreePSyncRequest::try_from(req)?; @@ -458,35 +452,33 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsSyncType::get_request_body( - self, req, connectors, - )?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) + .set_body(PaymentsSyncType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: transformers::BraintreePSyncResponse = res .response .parse_struct("Braintree PaymentSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -495,48 +487,42 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - _req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn build_request( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -545,10 +531,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -561,10 +547,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { match data.request.is_auto_capture()? { true => { let response: transformers::BraintreePaymentsResponse = res @@ -573,7 +559,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -621,34 +605,32 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn build_request( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsVoidType::get_request_body( - self, req, connectors, - )?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) .build(), )) } fn get_request_body( &self, - req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = transformers::BraintreeCancelRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -656,17 +638,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: transformers::BraintreeCancelResponse = res .response .parse_struct("Braintree VoidResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -674,7 +656,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -685,14 +667,12 @@ impl api::Refund for Braintree {} impl api::RefundExecute for Braintree {} impl api::RefundSync for Braintree {} -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -702,18 +682,18 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::RefundsRouterData, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -724,36 +704,32 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: transformers::BraintreeRefundResponse = res .response .parse_struct("Braintree RefundResponse") .change_context(errors::ConnectorError::RequestEncodingFailed)?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -762,21 +738,19 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Braintree -{ +impl ConnectorIntegration for Braintree { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -786,16 +760,16 @@ impl ConnectorIntegration CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::RefundSyncRouterData, - _connectors: &settings::Connectors, + req: &RefundSyncRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = transformers::BraintreeRSyncRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -803,38 +777,34 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) + .headers(RefundSyncType::get_headers(self, req, connectors)?) + .set_body(RefundSyncType::get_request_body(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult< - types::RouterData, - errors::ConnectorError, - > { + res: Response, + ) -> CustomResult, errors::ConnectorError> + { let response: transformers::BraintreeRSyncResponse = res .response .parse_struct("Braintree RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -842,7 +812,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -850,17 +820,17 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha1)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let notif_item = get_webhook_object_from_body(request.body) @@ -886,7 +856,7 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -900,7 +870,7 @@ impl api::IncomingWebhook for Braintree { async fn verify_webhook_source( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, merchant_id: &common_utils::id_type::MerchantId, connector_webhook_details: Option, _connector_account_details: crypto::Encryptable>, @@ -940,7 +910,7 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif = get_webhook_object_from_body(_request.body) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; @@ -959,19 +929,19 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?; - Ok(IncomingWebhookEvent::foreign_from(response.kind.as_str())) + Ok(get_status(response.kind.as_str())) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; @@ -983,27 +953,24 @@ impl api::IncomingWebhook for Braintree { fn get_webhook_api_response( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, _error_kind: Option, - ) -> CustomResult, errors::ConnectorError> - { - Ok(services::api::ApplicationResponse::TextPlain( - "[accepted]".to_string(), - )) + ) -> CustomResult, errors::ConnectorError> { + Ok(ApplicationResponse::TextPlain("[accepted]".to_string())) } fn get_dispute_details( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let notif = get_webhook_object_from_body(request.body) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; let response = decode_webhook_payload(notif.bt_payload.replace('\n', "").as_bytes())?; match response.dispute { - Some(dispute_data) => Ok(api::disputes::DisputePayload { - amount: connector_utils::to_currency_lower_unit( + Some(dispute_data) => Ok(DisputePayload { + amount: to_currency_lower_unit( dispute_data.amount_disputed.to_string(), dispute_data.currency_iso_code, )?, @@ -1036,16 +1003,15 @@ fn get_matching_webhook_signature( fn get_webhook_object_from_body( body: &[u8], -) -> CustomResult { - serde_urlencoded::from_bytes::(body).change_context( - errors::ParsingError::StructParseFailure("BraintreeWebhookResponse"), - ) +) -> CustomResult { + serde_urlencoded::from_bytes::(body) + .change_context(ParsingError::StructParseFailure("BraintreeWebhookResponse")) } fn decode_webhook_payload( payload: &[u8], ) -> CustomResult { - let decoded_response = consts::BASE64_ENGINE + let decoded_response = BASE64_ENGINE .decode(payload) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; @@ -1057,15 +1023,15 @@ fn decode_webhook_payload( .change_context(errors::ConnectorError::WebhookBodyDecodingFailed) } -impl services::ConnectorRedirectResponse for Braintree { +impl ConnectorRedirectResponse for Braintree { fn get_flow_type( &self, _query_params: &str, json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync => match json_payload { + PaymentAction::PSync => match json_payload { Some(payload) => { let redirection_response: transformers::BraintreeRedirectionResponse = serde_json::from_value(payload).change_context( @@ -1083,38 +1049,34 @@ impl services::ConnectorRedirectResponse for Braintree { braintree_response_payload.message, ), Err(_) => ( - consts::NO_ERROR_CODE.to_string(), + NO_ERROR_CODE.to_string(), redirection_response.authentication_response, ), }; - Ok(payments::CallConnectorAction::StatusUpdate { + Ok(CallConnectorAction::StatusUpdate { status: enums::AttemptStatus::AuthenticationFailed, error_code: Some(error_code), error_message: Some(error_message), }) } - None => Ok(payments::CallConnectorAction::Avoid), + None => Ok(CallConnectorAction::Avoid), }, - services::PaymentAction::CompleteAuthorize - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::CompleteAuthorize + | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } } } } -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Braintree +impl ConnectorIntegration + for Braintree { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { @@ -1122,17 +1084,17 @@ impl } fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(self.base_url(connectors).to_string()) } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -1146,20 +1108,20 @@ impl fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCompleteAuthorizeType::get_url( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::PaymentsCompleteAuthorizeType::get_headers( + .headers(PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -1167,12 +1129,11 @@ impl } fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { - match connector_utils::PaymentsCompleteAuthorizeRequestData::is_auto_capture(&data.request)? - { + res: Response, + ) -> CustomResult { + match PaymentsCompleteAuthorizeRequestData::is_auto_capture(&data.request)? { true => { let response: transformers::BraintreeCompleteChargeResponse = res .response @@ -1180,7 +1141,7 @@ impl .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -1193,7 +1154,7 @@ impl .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -1204,7 +1165,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) diff --git a/crates/router/src/connector/braintree/transformers.rs b/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs similarity index 84% rename from crates/router/src/connector/braintree/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs index 5dd6db5aa24..b8253cc411d 100644 --- a/crates/router/src/connector/braintree/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs @@ -1,19 +1,32 @@ +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::enums; use common_utils::{pii, types::StringMajorUnit}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, PaymentMethodToken, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{CompleteAuthorizeData, PaymentsAuthorizeData, ResponseId}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{self, RefundsRouterData}, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use crate::{ - connector::utils::{ + types::{PaymentsCaptureResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + unimplemented_payment_method, + utils::{ self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData, - RefundsRequestData, RouterData, + RefundsRequestData, RouterData as _, }, - consts, - core::errors, - services, - types::{self, api, domain, storage::enums, MandateReference}, - unimplemented_payment_method, }; pub const CHANNEL_CODE: &str = "HyperSwitchBT_Ecom"; pub const CLIENT_TOKEN_MUTATION: &str = "mutation createClientToken($input: CreateClientTokenInput!) { createClientToken(input: $input) { clientToken}}"; @@ -135,11 +148,11 @@ pub struct BraintreeAuthType { pub(super) private_key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for BraintreeAuthType { +impl TryFrom<&ConnectorAuthType> for BraintreeAuthType { type Error = error_stack::Report; - fn try_from(item: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { + fn try_from(item: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, api_secret, key1: _merchant_id, @@ -271,7 +284,7 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> Some(metadata.merchant_config_currency), )?; match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(_) => { + PaymentMethodData::Card(_) => { if item.router_data.is_three_ds() { Ok(Self::CardThreeDs(BraintreeClientTokenRequest::try_from( metadata, @@ -280,7 +293,7 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> Ok(Self::Card(CardPaymentRequest::try_from((item, metadata))?)) } } - domain::PaymentMethodData::MandatePayment => { + PaymentMethodData::MandatePayment => { let connector_mandate_id = item.router_data.request.connector_mandate_id().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "connector_mandate_id", @@ -292,23 +305,23 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsAuthorizeRouterData>> metadata, ))?)) } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("braintree"), ) @@ -400,21 +413,16 @@ pub struct AuthChargeCreditCard { impl TryFrom< - types::ResponseRouterData< - F, - BraintreeAuthResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - > for types::RouterData + ResponseRouterData, + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BraintreeAuthResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { @@ -426,7 +434,7 @@ impl let transaction_data = auth_response.data.authorize_credit_card.transaction; let status = enums::AttemptStatus::from(transaction_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: transaction_data.status.to_string().clone(), message: transaction_data.status.to_string().clone(), reason: Some(transaction_data.status.to_string().clone()), @@ -435,8 +443,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_data.id), redirection_data: Box::new(None), mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( |pm| MandateReference { @@ -461,8 +469,8 @@ impl } BraintreeAuthResponse::ClientTokenResponse(client_token_data) => Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(Some(get_braintree_redirect_form( *client_token_data, item.data.get_payment_method_token()?, @@ -484,7 +492,7 @@ impl fn build_error_response( response: &[ErrorDetails], http_code: u16, -) -> Result { +) -> Result { let error_messages = response .iter() .map(|error| error.message.to_string()) @@ -513,10 +521,10 @@ fn get_error_response( error_msg: Option, error_reason: Option, http_code: u16, -) -> Result { - Err(types::ErrorResponse { - code: error_code.unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), - message: error_msg.unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), +) -> Result { + Err(hyperswitch_domain_models::router_data::ErrorResponse { + code: error_code.unwrap_or_else(|| NO_ERROR_CODE.to_string()), + message: error_msg.unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: error_reason, status_code: http_code, attempt_status: None, @@ -576,21 +584,21 @@ impl From for enums::AttemptStatus { impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BraintreePaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BraintreePaymentsResponse, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, + PaymentsAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { @@ -602,7 +610,7 @@ impl let transaction_data = payment_response.data.charge_credit_card.transaction; let status = enums::AttemptStatus::from(transaction_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: transaction_data.status.to_string().clone(), message: transaction_data.status.to_string().clone(), reason: Some(transaction_data.status.to_string().clone()), @@ -611,8 +619,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_data.id), redirection_data: Box::new(None), mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( |pm| MandateReference { @@ -637,8 +645,8 @@ impl } BraintreePaymentsResponse::ClientTokenResponse(client_token_data) => Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(Some(get_braintree_redirect_form( *client_token_data, item.data.get_payment_method_token()?, @@ -659,21 +667,21 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BraintreeCompleteChargeResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BraintreeCompleteChargeResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { @@ -685,7 +693,7 @@ impl let transaction_data = payment_response.data.charge_credit_card.transaction; let status = enums::AttemptStatus::from(transaction_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: transaction_data.status.to_string().clone(), message: transaction_data.status.to_string().clone(), reason: Some(transaction_data.status.to_string().clone()), @@ -694,8 +702,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_data.id), redirection_data: Box::new(None), mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( |pm| MandateReference { @@ -724,21 +732,21 @@ impl impl TryFrom< - types::ResponseRouterData< + ResponseRouterData< F, BraintreeCompleteAuthResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, - > for types::RouterData + > for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, BraintreeCompleteAuthResponse, - types::CompleteAuthorizeData, - types::PaymentsResponseData, + CompleteAuthorizeData, + PaymentsResponseData, >, ) -> Result { match item.response { @@ -750,7 +758,7 @@ impl let transaction_data = auth_response.data.authorize_credit_card.transaction; let status = enums::AttemptStatus::from(transaction_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: transaction_data.status.to_string().clone(), message: transaction_data.status.to_string().clone(), reason: Some(transaction_data.status.to_string().clone()), @@ -759,8 +767,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_data.id), redirection_data: Box::new(None), mandate_reference: Box::new(transaction_data.payment_method.as_ref().map( |pm| MandateReference { @@ -835,11 +843,9 @@ pub struct BraintreeRefundInput { refund: RefundInputData, } -impl TryFrom>> for BraintreeRefundRequest { +impl TryFrom>> for BraintreeRefundRequest { type Error = error_stack::Report; - fn try_from( - item: BraintreeRouterData<&types::RefundsRouterData>, - ) -> Result { + fn try_from(item: BraintreeRouterData<&RefundsRouterData>) -> Result { let metadata: BraintreeMeta = utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone()) .change_context(errors::ConnectorError::InvalidConnectorConfig { @@ -907,12 +913,12 @@ pub struct RefundResponse { pub data: BraintreeRefundResponseData, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: match item.response { @@ -923,7 +929,7 @@ impl TryFrom), } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { match item.response { BraintreeRSyncResponse::ErrorResponse(error_response) => Ok(Self { @@ -1030,7 +1036,7 @@ impl TryFrom for BraintreeTokenRequest { type Error = error_stack::Report; fn try_from(item: &types::TokenizationRouterData) -> Result { match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(card_data) => Ok(Self { + PaymentMethodData::Card(card_data) => Ok(Self { query: TOKENIZE_CREDIT_CARD.to_string(), variables: VariableInput { input: InputData { @@ -1091,24 +1097,24 @@ impl TryFrom<&types::TokenizationRouterData> for BraintreeTokenRequest { }, }, }), - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("braintree"), ) @@ -1169,13 +1175,12 @@ pub enum BraintreeTokenResponse { ErrorResponse(Box), } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { response: match item.response { @@ -1184,7 +1189,7 @@ impl } BraintreeTokenResponse::TokenResponse(token_response) => { - Ok(types::PaymentsResponseData::TokenizationResponse { + Ok(PaymentsResponseData::TokenizationResponse { token: token_response .data .tokenize_credit_card @@ -1253,19 +1258,19 @@ pub struct CaptureResponse { data: CaptureResponseData, } -impl TryFrom> +impl TryFrom> for types::PaymentsCaptureRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsCaptureResponseRouterData, + item: PaymentsCaptureResponseRouterData, ) -> Result { match item.response { BraintreeCaptureResponse::SuccessResponse(capture_data) => { let transaction_data = capture_data.data.capture_transaction.transaction; let status = enums::AttemptStatus::from(transaction_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: transaction_data.status.to_string().clone(), message: transaction_data.status.to_string().clone(), reason: Some(transaction_data.status.to_string().clone()), @@ -1274,8 +1279,8 @@ impl TryFrom> status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(transaction_data.id), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(transaction_data.id), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -1358,13 +1363,12 @@ pub enum BraintreeCancelResponse { ErrorResponse(Box), } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { match item.response { BraintreeCancelResponse::ErrorResponse(error_response) => Ok(Self { @@ -1375,7 +1379,7 @@ impl let void_data = void_response.data.reverse_transaction.reversal; let status = enums::AttemptStatus::from(void_data.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: void_data.status.to_string().clone(), message: void_data.status.to_string().clone(), reason: Some(void_data.status.to_string().clone()), @@ -1384,8 +1388,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -1455,13 +1459,12 @@ pub struct PSyncResponse { data: PSyncResponseData, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { match item.response { BraintreePSyncResponse::ErrorResponse(error_response) => Ok(Self { @@ -1478,7 +1481,7 @@ impl .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?; let status = enums::AttemptStatus::from(edge_data.node.status.clone()); let response = if utils::is_payment_failure(status) { - Err(types::ErrorResponse { + Err(hyperswitch_domain_models::router_data::ErrorResponse { code: edge_data.node.status.to_string().clone(), message: edge_data.node.status.to_string().clone(), reason: Some(edge_data.node.status.to_string().clone()), @@ -1487,10 +1490,8 @@ impl status_code: item.http_code, }) } else { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - edge_data.node.id.clone(), - ), + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(edge_data.node.id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -1591,14 +1592,14 @@ impl variables: VariablePaymentInput { input: PaymentInput { payment_method_id: match item.router_data.get_payment_method_token()? { - types::PaymentMethodToken::Token(token) => token, - types::PaymentMethodToken::ApplePayDecrypt(_) => Err( + PaymentMethodToken::Token(token) => token, + PaymentMethodToken::ApplePayDecrypt(_) => Err( unimplemented_payment_method!("Apple Pay", "Simplified", "Braintree"), )?, - types::PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Braintree"))? } - types::PaymentMethodToken::GooglePayDecrypt(_) => { + PaymentMethodToken::GooglePayDecrypt(_) => { Err(unimplemented_payment_method!("Google Pay", "Braintree"))? } }, @@ -1683,51 +1684,49 @@ impl TryFrom<&BraintreeRouterData<&types::PaymentsCompleteAuthorizeRouterData>> fn get_braintree_redirect_form( client_token_data: ClientTokenResponse, - payment_method_token: types::PaymentMethodToken, - card_details: domain::PaymentMethodData, -) -> Result> { - Ok(services::RedirectForm::Braintree { + payment_method_token: PaymentMethodToken, + card_details: PaymentMethodData, +) -> Result> { + Ok(RedirectForm::Braintree { client_token: client_token_data .data .create_client_token .client_token .expose(), card_token: match payment_method_token { - types::PaymentMethodToken::Token(token) => token.expose(), - types::PaymentMethodToken::ApplePayDecrypt(_) => Err(unimplemented_payment_method!( + PaymentMethodToken::Token(token) => token.expose(), + PaymentMethodToken::ApplePayDecrypt(_) => Err(unimplemented_payment_method!( "Apple Pay", "Simplified", "Braintree" ))?, - types::PaymentMethodToken::PazeDecrypt(_) => { + PaymentMethodToken::PazeDecrypt(_) => { Err(unimplemented_payment_method!("Paze", "Braintree"))? } - types::PaymentMethodToken::GooglePayDecrypt(_) => { + PaymentMethodToken::GooglePayDecrypt(_) => { Err(unimplemented_payment_method!("Google Pay", "Braintree"))? } }, bin: match card_details { - domain::PaymentMethodData::Card(card_details) => { - card_details.card_number.get_card_isin() - } - domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Err( + PaymentMethodData::Card(card_details) => card_details.card_number.get_card_isin(), + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => Err( errors::ConnectorError::NotImplemented("given payment method".to_owned()), )?, }, @@ -1747,17 +1746,16 @@ pub struct Notification { pub timestamp: String, pub dispute: Option, } -impl types::transformers::ForeignFrom<&str> for api_models::webhooks::IncomingWebhookEvent { - fn foreign_from(status: &str) -> Self { - match status { - "dispute_opened" => Self::DisputeOpened, - "dispute_lost" => Self::DisputeLost, - "dispute_won" => Self::DisputeWon, - "dispute_accepted" | "dispute_auto_accepted" => Self::DisputeAccepted, - "dispute_expired" => Self::DisputeExpired, - "dispute_disputed" => Self::DisputeChallenged, - _ => Self::EventNotSupported, - } + +pub(crate) fn get_status(status: &str) -> IncomingWebhookEvent { + match status { + "dispute_opened" => IncomingWebhookEvent::DisputeOpened, + "dispute_lost" => IncomingWebhookEvent::DisputeLost, + "dispute_won" => IncomingWebhookEvent::DisputeWon, + "dispute_accepted" | "dispute_auto_accepted" => IncomingWebhookEvent::DisputeAccepted, + "dispute_expired" => IncomingWebhookEvent::DisputeExpired, + "dispute_disputed" => IncomingWebhookEvent::DisputeChallenged, + _ => IncomingWebhookEvent::EventNotSupported, } } diff --git a/crates/router/src/connector/globalpay.rs b/crates/hyperswitch_connectors/src/connectors/globalpay.rs similarity index 61% rename from crates/router/src/connector/globalpay.rs rename to crates/hyperswitch_connectors/src/connectors/globalpay.rs index 2e971f8034a..6d233bcfc33 100644 --- a/crates/router/src/connector/globalpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/globalpay.rs @@ -1,42 +1,68 @@ mod requests; -use super::utils as connector_utils; mod response; pub mod transformers; -use ::common_utils::{errors::ReportSwitchExt, ext_traits::ByteSliceExt, request::RequestContent}; -use common_utils::types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}; -use diesel_models::enums; -use error_stack::ResultExt; -use masking::PeekInterface; -use serde_json::Value; -use self::{ - requests::{GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest}, - response::{ - GlobalpayPaymentsResponse, GlobalpayRefreshTokenErrorResponse, - GlobalpayRefreshTokenResponse, +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::{enums, CallConnectorAction, PaymentAction}; +use common_utils::{ + crypto, + errors::{CustomResult, ReportSwitchExt}, + ext_traits::{ByteSliceExt, BytesExt}, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + CompleteAuthorize, + }, + router_request_types::{ + AccessTokenRequestData, CompleteAuthorizeData, PaymentMethodTokenizationData, + PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, SyncRequestType, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsCompleteAuthorizeRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, }; -use super::utils::{PaymentMethodDataType, RefundsRequestData}; -use crate::{ - configs::settings, - core::{ - errors::{self, CustomResult}, - payments, +use hyperswitch_interfaces::{ + api::{ + self, CaptureSyncMethod, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, + ConnectorRedirectResponse, ConnectorSpecifications, ConnectorValidation, + PaymentsCompleteAuthorize, }, + configs::Connectors, + errors, events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, - }, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt, PaymentsCompleteAuthorize}, - transformers::ForeignTryFrom, - ErrorResponse, + PaymentsAuthorizeType, PaymentsCaptureType, PaymentsCompleteAuthorizeType, + PaymentsSyncType, PaymentsVoidType, RefreshTokenType, RefundExecuteType, RefundSyncType, + Response, + }, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use requests::{GlobalpayPaymentsRequest, GlobalpayRefreshTokenRequest}; +use response::{ + GlobalpayPaymentsResponse, GlobalpayRefreshTokenErrorResponse, GlobalpayRefreshTokenResponse, +}; +use serde_json::Value; + +use crate::{ + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils::{ + construct_not_supported_error_report, convert_amount, get_header_key_value, + is_mandate_supported, ForeignTryFrom, PaymentMethodDataType, RefundsRequestData, }, - utils::{crypto, BytesExt}, }; #[derive(Clone)] @@ -58,9 +84,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let access_token = req .access_token .clone() @@ -69,7 +95,7 @@ where Ok(vec![ ( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self) + PaymentsAuthorizeType::get_content_type(self) .to_string() .into(), ), @@ -91,20 +117,20 @@ impl ConnectorCommon for Globalpay { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.globalpay.base_url.as_ref() } fn get_auth_header( &self, - _auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![]) } fn build_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { let response: transformers::GlobalpayErrorResponse = res @@ -137,17 +163,16 @@ impl ConnectorValidation for Globalpay { match capture_method { enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual - | enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::SequentialAutomatic => Ok(()), - enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_implemented_error_report(capture_method, self.id()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + construct_not_supported_error_report(capture_method, self.id()), ), } } fn validate_mandate_payment( &self, - pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_type: Option, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([ PaymentMethodDataType::Card, @@ -158,24 +183,20 @@ impl ConnectorValidation for Globalpay { PaymentMethodDataType::Eps, PaymentMethodDataType::Giropay, ]); - connector_utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } impl PaymentsCompleteAuthorize for Globalpay {} -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Globalpay +impl ConnectorIntegration + for Globalpay { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -185,8 +206,8 @@ impl fn get_url( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}transactions/{}/confirmation", @@ -200,28 +221,28 @@ impl fn get_request_body( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { Ok(RequestContent::Json(Box::new(serde_json::json!({})))) } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCompleteAuthorizeType::get_url( + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) .attach_default_headers() - .headers(types::PaymentsCompleteAuthorizeType::get_headers( + .headers(PaymentsCompleteAuthorizeType::get_headers( self, req, connectors, )?) - .set_body(types::PaymentsCompleteAuthorizeType::get_request_body( + .set_body(PaymentsCompleteAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -230,10 +251,10 @@ impl fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("Globalpay PaymentsResponse") @@ -242,7 +263,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -252,7 +273,7 @@ impl fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -261,20 +282,16 @@ impl impl api::ConnectorAccessToken for Globalpay {} -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - _req: &types::RefreshTokenRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![ ( headers::CONTENT_TYPE.to_string(), - types::RefreshTokenType::get_content_type(self) - .to_string() - .into(), + RefreshTokenType::get_content_type(self).to_string().into(), ), ("X-GP-Version".to_string(), "2021-03-22".to_string().into()), ]) @@ -286,34 +303,32 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}{}", self.base_url(connectors), "accesstoken")) } fn build_request( &self, - req: &types::RefreshTokenRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&RefreshTokenType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) - .set_body(types::RefreshTokenType::get_request_body( - self, req, connectors, - )?) + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) .build(), )) } fn get_request_body( &self, - req: &types::RefreshTokenRouterData, - _connectors: &settings::Connectors, + req: &RefreshTokenRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = GlobalpayRefreshTokenRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -321,10 +336,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayRefreshTokenResponse = res .response .parse_struct("Globalpay PaymentsResponse") @@ -333,7 +348,7 @@ impl ConnectorIntegration, ) -> CustomResult { let response: GlobalpayRefreshTokenErrorResponse = res @@ -369,33 +384,21 @@ impl api::Payment for Globalpay {} impl api::PaymentToken for Globalpay {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Globalpay +impl ConnectorIntegration + for Globalpay { // Not Implemented (R) } impl api::MandateSetup for Globalpay {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Globalpay +impl ConnectorIntegration + for Globalpay { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Globalpay".to_string()) .into(), @@ -405,14 +408,12 @@ impl impl api::PaymentVoid for Globalpay {} -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -422,8 +423,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}/transactions/{}/reversal", @@ -434,34 +435,32 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) - .set_body(types::PaymentsVoidType::get_request_body( - self, req, connectors, - )?) + .headers(PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(PaymentsVoidType::get_request_body(self, req, connectors)?) .build(), )) } fn get_request_body( &self, - req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, ) -> CustomResult { let amount = req .request .minor_amount .and_then(|amount| { - req.request.currency.map(|currency| { - connector_utils::convert_amount(self.amount_converter, amount, currency) - }) + req.request + .currency + .map(|currency| convert_amount(self.amount_converter, amount, currency)) }) .transpose()?; let connector_router_data = requests::GlobalpayCancelRouterData::from((amount, req)); @@ -471,17 +470,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("Globalpay PaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -491,7 +490,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -499,14 +498,12 @@ impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -516,8 +513,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}transactions/{}", @@ -531,22 +528,22 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .headers(PaymentsSyncType::get_headers(self, req, connectors)?) .build(), )) } fn get_error_response( &self, - res: types::Response, + res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -554,10 +551,10 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("globalpay PaymentsResponse") @@ -567,11 +564,11 @@ impl ConnectorIntegration true, - types::SyncRequestType::SinglePaymentSync => false, + SyncRequestType::MultipleCaptureSync(_) => true, + SyncRequestType::SinglePaymentSync => false, }; - types::RouterData::foreign_try_from(( - types::ResponseRouterData { + RouterData::foreign_try_from(( + ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -582,20 +579,18 @@ impl ConnectorIntegration CustomResult { - Ok(services::CaptureSyncMethod::Individual) + ) -> CustomResult { + Ok(CaptureSyncMethod::Individual) } } impl api::PaymentCapture for Globalpay {} -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -605,8 +600,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}/transactions/{}/capture", @@ -617,10 +612,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -633,18 +628,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( + .headers(PaymentsCaptureType::get_headers(self, req, connectors)?) + .set_body(PaymentsCaptureType::get_request_body( self, req, connectors, )?) .build(), @@ -653,17 +646,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("Globalpay PaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -673,7 +666,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -682,21 +675,16 @@ impl ConnectorIntegration - for Globalpay -{ -} +impl ConnectorIntegration for Globalpay {} impl api::PaymentAuthorize for Globalpay {} -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -706,18 +694,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}transactions", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -730,20 +718,16 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::PaymentsAuthorizeType::get_url( - self, req, connectors, - )?) + RequestBuilder::new() + .method(Method::Post) + .url(&PaymentsAuthorizeType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::PaymentsAuthorizeType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsAuthorizeType::get_request_body( + .headers(PaymentsAuthorizeType::get_headers(self, req, connectors)?) + .set_body(PaymentsAuthorizeType::get_request_body( self, req, connectors, )?) .build(), @@ -752,17 +736,17 @@ impl ConnectorIntegration, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("Globalpay PaymentsResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -772,7 +756,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -783,14 +767,12 @@ impl api::Refund for Globalpay {} impl api::RefundExecute for Globalpay {} impl api::RefundSync for Globalpay {} -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -800,8 +782,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}transactions/{}/refund", @@ -812,10 +794,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -828,29 +810,25 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) + .headers(RefundExecuteType::get_headers(self, req, connectors)?) + .set_body(RefundExecuteType::get_request_body(self, req, connectors)?) .build(); Ok(Some(request)) } fn handle_response( &self, - data: &types::RefundsRouterData, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult, errors::ConnectorError> { + res: Response, + ) -> CustomResult, errors::ConnectorError> { let response: GlobalpayPaymentsResponse = res .response .parse_struct("globalpay RefundResponse") @@ -859,7 +837,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) } } -impl ConnectorIntegration - for Globalpay -{ +impl ConnectorIntegration for Globalpay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -893,8 +869,8 @@ impl ConnectorIntegration CustomResult { let refund_id = req.request.get_connector_refund_id()?; Ok(format!( @@ -906,32 +882,32 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) + RequestBuilder::new() + .method(Method::Get) + .url(&RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .headers(RefundSyncType::get_headers(self, req, connectors)?) .build(), )) } fn handle_response( &self, - data: &types::RefundSyncRouterData, + data: &RefundSyncRouterData, event_builder: Option<&mut ConnectorEvent>, - res: types::Response, - ) -> CustomResult { + res: Response, + ) -> CustomResult { let response: GlobalpayPaymentsResponse = res .response .parse_struct("globalpay RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -941,7 +917,7 @@ impl ConnectorIntegration, ) -> CustomResult { self.build_error_response(res, event_builder) @@ -949,26 +925,26 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::Sha512)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let signature = connector_utils::get_header_key_value("x-gp-signature", request.headers)?; + let signature = get_header_key_value("x-gp-signature", request.headers)?; Ok(signature.as_bytes().to_vec()) } fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -983,7 +959,7 @@ impl api::IncomingWebhook for Globalpay { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let details: response::GlobalpayWebhookObjectId = request .body @@ -996,28 +972,26 @@ impl api::IncomingWebhook for Globalpay { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let details: response::GlobalpayWebhookObjectEventType = request .body .parse_struct("GlobalpayWebhookObjectEventType") .switch()?; Ok(match details.status { response::GlobalpayWebhookStatus::Declined => { - api::IncomingWebhookEvent::PaymentIntentFailure + IncomingWebhookEvent::PaymentIntentFailure } response::GlobalpayWebhookStatus::Captured => { - api::IncomingWebhookEvent::PaymentIntentSuccess - } - response::GlobalpayWebhookStatus::Unknown => { - api::IncomingWebhookEvent::EventNotSupported + IncomingWebhookEvent::PaymentIntentSuccess } + response::GlobalpayWebhookStatus::Unknown => IncomingWebhookEvent::EventNotSupported, }) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new( request @@ -1028,18 +1002,18 @@ impl api::IncomingWebhook for Globalpay { } } -impl services::ConnectorRedirectResponse for Globalpay { +impl ConnectorRedirectResponse for Globalpay { fn get_flow_type( &self, _query_params: &str, _json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::CompleteAuthorize - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync + | PaymentAction::CompleteAuthorize + | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } } } diff --git a/crates/router/src/connector/globalpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/globalpay/requests.rs similarity index 100% rename from crates/router/src/connector/globalpay/requests.rs rename to crates/hyperswitch_connectors/src/connectors/globalpay/requests.rs diff --git a/crates/router/src/connector/globalpay/response.rs b/crates/hyperswitch_connectors/src/connectors/globalpay/response.rs similarity index 100% rename from crates/router/src/connector/globalpay/response.rs rename to crates/hyperswitch_connectors/src/connectors/globalpay/response.rs diff --git a/crates/router/src/connector/globalpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/globalpay/transformers.rs similarity index 65% rename from crates/router/src/connector/globalpay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/globalpay/transformers.rs index 8008cc309c2..2b74b5f8cd4 100644 --- a/crates/router/src/connector/globalpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/globalpay/transformers.rs @@ -1,8 +1,24 @@ use common_utils::{ crypto::{self, GenerateDigest}, + errors::ParsingError, + request::Method, types::{AmountConvertor, MinorUnit, StringMinorUnit, StringMinorUnitForConnector}, }; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundExecuteRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{consts::NO_ERROR_MESSAGE, errors}; use masking::{ExposeInterface, PeekInterface, Secret}; use rand::distributions::DistString; use serde::{Deserialize, Serialize}; @@ -17,11 +33,12 @@ use super::{ response::{GlobalpayPaymentStatus, GlobalpayPaymentsResponse, GlobalpayRefreshTokenResponse}, }; use crate::{ - connector::utils::{self, CardData, PaymentsAuthorizeRequestData, RouterData, WalletData}, - consts, - core::errors, - services::{self, RedirectForm}, - types::{self, api, domain, storage::enums, transformers::ForeignTryFrom, ErrorResponse}, + types::{PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + construct_captures_response_hashmap, to_connector_meta_from_secret, CardData, + ForeignTryFrom, MultipleCaptureSyncResponse, PaymentsAuthorizeRequestData, RouterData as _, + WalletData, + }, }; impl From<(StringMinorUnit, T)> for GlobalPayRouterData { @@ -49,15 +66,13 @@ pub struct GlobalPayMeta { account_name: Secret, } -impl TryFrom<&GlobalPayRouterData<&types::PaymentsAuthorizeRouterData>> - for GlobalpayPaymentsRequest -{ +impl TryFrom<&GlobalPayRouterData<&PaymentsAuthorizeRouterData>> for GlobalpayPaymentsRequest { type Error = Error; fn try_from( - item: &GlobalPayRouterData<&types::PaymentsAuthorizeRouterData>, + item: &GlobalPayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { let metadata: GlobalPayMeta = - utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; + to_connector_meta_from_secret(item.router_data.connector_meta_data.clone())?; let account_name = metadata.account_name; let (initiator, stored_credential, brand_reference) = get_mandate_details(item.router_data)?; @@ -116,12 +131,12 @@ impl TryFrom<&GlobalPayRouterData<&types::PaymentsAuthorizeRouterData>> } } -impl TryFrom<&GlobalPayRouterData<&types::PaymentsCaptureRouterData>> +impl TryFrom<&GlobalPayRouterData<&PaymentsCaptureRouterData>> for requests::GlobalpayCaptureRequest { type Error = Error; fn try_from( - value: &GlobalPayRouterData<&types::PaymentsCaptureRouterData>, + value: &GlobalPayRouterData<&PaymentsCaptureRouterData>, ) -> Result { Ok(Self { amount: Some(value.amount.to_owned()), @@ -147,12 +162,12 @@ impl TryFrom<&GlobalPayRouterData<&types::PaymentsCaptureRouterData>> } } -impl TryFrom<&GlobalpayCancelRouterData<&types::PaymentsCancelRouterData>> +impl TryFrom<&GlobalpayCancelRouterData<&PaymentsCancelRouterData>> for requests::GlobalpayCancelRequest { type Error = Error; fn try_from( - value: &GlobalpayCancelRouterData<&types::PaymentsCancelRouterData>, + value: &GlobalpayCancelRouterData<&PaymentsCancelRouterData>, ) -> Result { Ok(Self { amount: value.amount.clone(), @@ -165,11 +180,11 @@ pub struct GlobalpayAuthType { pub key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType { +impl TryFrom<&ConnectorAuthType> for GlobalpayAuthType { type Error = Error; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { app_id: key1.to_owned(), key: api_key.to_owned(), }), @@ -178,8 +193,8 @@ impl TryFrom<&types::ConnectorAuthType> for GlobalpayAuthType { } } -impl TryFrom for types::AccessToken { - type Error = error_stack::Report; +impl TryFrom for AccessToken { + type Error = error_stack::Report; fn try_from(item: GlobalpayRefreshTokenResponse) -> Result { Ok(Self { @@ -189,10 +204,10 @@ impl TryFrom for types::AccessToken { } } -impl TryFrom<&types::RefreshTokenRouterData> for GlobalpayRefreshTokenRequest { +impl TryFrom<&RefreshTokenRouterData> for GlobalpayRefreshTokenRequest { type Error = Error; - fn try_from(item: &types::RefreshTokenRouterData) -> Result { + fn try_from(item: &RefreshTokenRouterData) -> Result { let globalpay_auth = GlobalpayAuthType::try_from(&item.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType) .attach_printable("Could not convert connector_auth to globalpay_auth")?; @@ -215,7 +230,7 @@ impl TryFrom<&types::RefreshTokenRouterData> for GlobalpayRefreshTokenRequest { } } -impl From for enums::AttemptStatus { +impl From for common_enums::AttemptStatus { fn from(item: GlobalpayPaymentStatus) -> Self { match item { GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Charged, @@ -228,7 +243,7 @@ impl From for enums::AttemptStatus { } } -impl From for enums::RefundStatus { +impl From for common_enums::RefundStatus { fn from(item: GlobalpayPaymentStatus) -> Self { match item { GlobalpayPaymentStatus::Captured | GlobalpayPaymentStatus::Funded => Self::Success, @@ -239,26 +254,26 @@ impl From for enums::RefundStatus { } } -impl From> for requests::CaptureMode { - fn from(capture_method: Option) -> Self { +impl From> for requests::CaptureMode { + fn from(capture_method: Option) -> Self { match capture_method { - Some(enums::CaptureMethod::Manual) => Self::Later, - Some(enums::CaptureMethod::ManualMultiple) => Self::Multiple, + Some(common_enums::CaptureMethod::Manual) => Self::Later, + Some(common_enums::CaptureMethod::ManualMultiple) => Self::Multiple, _ => Self::Auto, } } } fn get_payment_response( - status: enums::AttemptStatus, + status: common_enums::AttemptStatus, response: GlobalpayPaymentsResponse, redirection_data: Option, -) -> Result { +) -> Result { let mandate_reference = response.payment_method.as_ref().and_then(|pm| { pm.card .as_ref() .and_then(|card| card.brand_reference.to_owned()) - .map(|id| types::MandateReference { + .map(|id| MandateReference { connector_mandate_id: Some(id.expose()), payment_method_id: None, mandate_metadata: None, @@ -266,15 +281,15 @@ fn get_payment_response( }) }); match status { - enums::AttemptStatus::Failure => Err(ErrorResponse { + common_enums::AttemptStatus::Failure => Err(ErrorResponse { message: response .payment_method .and_then(|pm| pm.message) - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), ..Default::default() }), - _ => Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(response.id), + _ => Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.id), redirection_data: Box::new(redirection_data), mandate_reference: Box::new(mandate_reference), connector_metadata: None, @@ -286,20 +301,14 @@ fn get_payment_response( } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = Error; fn try_from( - item: types::ResponseRouterData< - F, - GlobalpayPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { - let status = enums::AttemptStatus::from(item.response.status); + let status = common_enums::AttemptStatus::from(item.response.status); let redirect_url = item .response .payment_method @@ -315,8 +324,7 @@ impl Url::parse(url).change_context(errors::ConnectorError::FailedToObtainIntegrationUrl) }) .transpose()?; - let redirection_data = - redirect_url.map(|url| RedirectForm::from((url, services::Method::Get))); + let redirection_data = redirect_url.map(|url| RedirectForm::from((url, Method::Get))); Ok(Self { status, response: get_payment_response(status, item.response, redirection_data), @@ -327,23 +335,23 @@ impl impl ForeignTryFrom<( - types::PaymentsSyncResponseRouterData, + PaymentsSyncResponseRouterData, bool, - )> for types::PaymentsSyncRouterData + )> for PaymentsSyncRouterData { type Error = Error; fn foreign_try_from( (value, is_multiple_capture_sync): ( - types::PaymentsSyncResponseRouterData, + PaymentsSyncResponseRouterData, bool, ), ) -> Result { if is_multiple_capture_sync { let capture_sync_response_list = - utils::construct_captures_response_hashmap(vec![value.response])?; + construct_captures_response_hashmap(vec![value.response])?; Ok(Self { - response: Ok(types::PaymentsResponseData::MultipleCaptureResponse { + response: Ok(PaymentsResponseData::MultipleCaptureResponse { capture_sync_response_list, }), ..value.data @@ -354,16 +362,15 @@ impl } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { - type Error = error_stack::Report; + type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::AccessToken { + response: Ok(AccessToken { token: item.response.token, expires: item.response.seconds_to_expire, }), @@ -372,47 +379,43 @@ impl } } -impl TryFrom<&GlobalPayRouterData<&types::RefundsRouterData>> - for requests::GlobalpayRefundRequest -{ +impl TryFrom<&GlobalPayRouterData<&RefundsRouterData>> for requests::GlobalpayRefundRequest { type Error = Error; - fn try_from( - item: &GlobalPayRouterData<&types::RefundsRouterData>, - ) -> Result { + fn try_from(item: &GlobalPayRouterData<&RefundsRouterData>) -> Result { Ok(Self { amount: item.amount.to_owned(), }) } } -impl TryFrom> - for types::RefundExecuteRouterData +impl TryFrom> + for RefundExecuteRouterData { type Error = Error; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, - refund_status: enums::RefundStatus::from(item.response.status), + refund_status: common_enums::RefundStatus::from(item.response.status), }), ..item.data }) } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for RefundsRouterData { type Error = Error; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.id, - refund_status: enums::RefundStatus::from(item.response.status), + refund_status: common_enums::RefundStatus::from(item.response.status), }), ..item.data }) @@ -427,28 +430,30 @@ pub struct GlobalpayErrorResponse { } fn get_payment_method_data( - item: &types::PaymentsAuthorizeRouterData, + item: &PaymentsAuthorizeRouterData, brand_reference: Option, ) -> Result { match &item.request.payment_method_data { - domain::PaymentMethodData::Card(ccard) => Ok(PaymentMethodData::Card(requests::Card { - number: ccard.card_number.clone(), - expiry_month: ccard.card_exp_month.clone(), - expiry_year: ccard.get_card_expiry_year_2_digit()?, - cvv: ccard.card_cvc.clone(), - account_type: None, - authcode: None, - avs_address: None, - avs_postal_code: None, - brand_reference, - chip_condition: None, - funding: None, - pin_block: None, - tag: None, - track: None, - })), - domain::PaymentMethodData::Wallet(wallet_data) => get_wallet_data(wallet_data), - domain::PaymentMethodData::BankRedirect(bank_redirect) => { + payment_method_data::PaymentMethodData::Card(ccard) => { + Ok(PaymentMethodData::Card(requests::Card { + number: ccard.card_number.clone(), + expiry_month: ccard.card_exp_month.clone(), + expiry_year: ccard.get_card_expiry_year_2_digit()?, + cvv: ccard.card_cvc.clone(), + account_type: None, + authcode: None, + avs_address: None, + avs_postal_code: None, + brand_reference, + chip_condition: None, + funding: None, + pin_block: None, + tag: None, + track: None, + })) + } + payment_method_data::PaymentMethodData::Wallet(wallet_data) => get_wallet_data(wallet_data), + payment_method_data::PaymentMethodData::BankRedirect(bank_redirect) => { PaymentMethodData::try_from(bank_redirect) } _ => Err(errors::ConnectorError::NotImplemented( @@ -457,17 +462,17 @@ fn get_payment_method_data( } } -fn get_return_url(item: &types::PaymentsAuthorizeRouterData) -> Option { +fn get_return_url(item: &PaymentsAuthorizeRouterData) -> Option { match item.request.payment_method_data.clone() { - domain::PaymentMethodData::Wallet(domain::WalletData::PaypalRedirect(_)) => { - item.request.complete_authorize_url.clone() - } + payment_method_data::PaymentMethodData::Wallet( + payment_method_data::WalletData::PaypalRedirect(_), + ) => item.request.complete_authorize_url.clone(), _ => item.request.router_return_url.clone(), } } type MandateDetails = (Option, Option, Option); -fn get_mandate_details(item: &types::PaymentsAuthorizeRouterData) -> Result { +fn get_mandate_details(item: &PaymentsAuthorizeRouterData) -> Result { Ok(if item.request.is_mandate_payment() { let connector_mandate_id = item.request.mandate_id.as_ref().and_then(|mandate_ids| { match mandate_ids.mandate_reference_id.clone() { @@ -496,12 +501,16 @@ fn get_mandate_details(item: &types::PaymentsAuthorizeRouterData) -> Result Result { +fn get_wallet_data( + wallet_data: &payment_method_data::WalletData, +) -> Result { match wallet_data { - domain::WalletData::PaypalRedirect(_) => Ok(PaymentMethodData::Apm(requests::Apm { - provider: Some(ApmProvider::Paypal), - })), - domain::WalletData::GooglePay(_) => { + payment_method_data::WalletData::PaypalRedirect(_) => { + Ok(PaymentMethodData::Apm(requests::Apm { + provider: Some(ApmProvider::Paypal), + })) + } + payment_method_data::WalletData::GooglePay(_) => { Ok(PaymentMethodData::DigitalWallet(requests::DigitalWallet { provider: Some(requests::DigitalWalletProvider::PayByGoogle), payment_token: wallet_data.get_wallet_token_as_json("Google Pay".to_string())?, @@ -513,20 +522,20 @@ fn get_wallet_data(wallet_data: &domain::WalletData) -> Result for PaymentMethodData { +impl TryFrom<&payment_method_data::BankRedirectData> for PaymentMethodData { type Error = Error; - fn try_from(value: &domain::BankRedirectData) -> Result { + fn try_from(value: &payment_method_data::BankRedirectData) -> Result { match value { - domain::BankRedirectData::Eps { .. } => Ok(Self::Apm(requests::Apm { + payment_method_data::BankRedirectData::Eps { .. } => Ok(Self::Apm(requests::Apm { provider: Some(ApmProvider::Eps), })), - domain::BankRedirectData::Giropay { .. } => Ok(Self::Apm(requests::Apm { + payment_method_data::BankRedirectData::Giropay { .. } => Ok(Self::Apm(requests::Apm { provider: Some(ApmProvider::Giropay), })), - domain::BankRedirectData::Ideal { .. } => Ok(Self::Apm(requests::Apm { + payment_method_data::BankRedirectData::Ideal { .. } => Ok(Self::Apm(requests::Apm { provider: Some(ApmProvider::Ideal), })), - domain::BankRedirectData::Sofort { .. } => Ok(Self::Apm(requests::Apm { + payment_method_data::BankRedirectData::Sofort { .. } => Ok(Self::Apm(requests::Apm { provider: Some(ApmProvider::Sofort), })), _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -534,22 +543,20 @@ impl TryFrom<&domain::BankRedirectData> for PaymentMethodData { } } -impl utils::MultipleCaptureSyncResponse for GlobalpayPaymentsResponse { +impl MultipleCaptureSyncResponse for GlobalpayPaymentsResponse { fn get_connector_capture_id(&self) -> String { self.id.clone() } - fn get_capture_attempt_status(&self) -> diesel_models::enums::AttemptStatus { - enums::AttemptStatus::from(self.status) + fn get_capture_attempt_status(&self) -> common_enums::AttemptStatus { + common_enums::AttemptStatus::from(self.status) } fn is_capture_response(&self) -> bool { true } - fn get_amount_captured( - &self, - ) -> Result, error_stack::Report> { + fn get_amount_captured(&self) -> Result, error_stack::Report> { match self.amount.clone() { Some(amount) => { let minor_amount = StringMinorUnitForConnector::convert_back( diff --git a/crates/router/src/connector/iatapay.rs b/crates/hyperswitch_connectors/src/connectors/iatapay.rs similarity index 71% rename from crates/router/src/connector/iatapay.rs rename to crates/hyperswitch_connectors/src/connectors/iatapay.rs index e694968ee04..9db00b61101 100644 --- a/crates/router/src/connector/iatapay.rs +++ b/crates/hyperswitch_connectors/src/connectors/iatapay.rs @@ -1,35 +1,53 @@ pub mod transformers; +use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; use common_utils::{ + consts::BASE64_ENGINE, crypto, - ext_traits::ByteSliceExt, - request::RequestContent, + errors::CustomResult, + ext_traits::{ByteSliceExt, BytesExt}, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; use error_stack::ResultExt; -use masking::PeekInterface; -use transformers as iatapay; - -use self::iatapay::IatapayPaymentsResponse; -use super::utils::{self as connector_utils, base64_decode}; -use crate::{ - configs::settings, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorSpecifications, ConnectorValidation, +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, Response, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, }, - utils::BytesExt, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; +use transformers::{self as iatapay, IatapayPaymentsResponse}; + +use crate::{ + constants::{headers, CONNECTOR_UNAUTHORIZED_ERROR}, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils::{base64_decode, convert_amount, get_header_key_value}, }; #[derive(Clone)] @@ -58,12 +76,8 @@ impl api::RefundExecute for Iatapay {} impl api::RefundSync for Iatapay {} impl api::PaymentToken for Iatapay {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Iatapay +impl ConnectorIntegration + for Iatapay { // Not Implemented (R) } @@ -74,9 +88,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut headers = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -108,14 +122,14 @@ impl ConnectorCommon for Iatapay { api::CurrencyUnit::Base } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.iatapay.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = iatapay::IatapayAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -132,9 +146,9 @@ impl ConnectorCommon for Iatapay { let response_error_message = if res.response.is_empty() && res.status_code == 401 { ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), - reason: Some(consts::CONNECTOR_UNAUTHORIZED_ERROR.to_string()), + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), + reason: Some(CONNECTOR_UNAUTHORIZED_ERROR.to_string()), attempt_status: None, connector_transaction_id: None, } @@ -149,9 +163,7 @@ impl ConnectorCommon for Iatapay { ErrorResponse { status_code: res.status_code, code: response.error, - message: response - .message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), reason: response.reason, attempt_status: None, connector_transaction_id: None, @@ -163,19 +175,15 @@ impl ConnectorCommon for Iatapay { impl ConnectorValidation for Iatapay {} -impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { //TODO: implement sessions flow } -impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn get_url( &self, - _req: &types::RefreshTokenRouterData, - connectors: &settings::Connectors, + _req: &RefreshTokenRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}{}", self.base_url(connectors), "/oauth/token")) } @@ -186,15 +194,15 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let auth: iatapay::IatapayAuthType = iatapay::IatapayAuthType::try_from(&req.connector_auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let auth_id = format!("{}:{}", auth.client_id.peek(), auth.client_secret.peek()); - let auth_val: String = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_id)); + let auth_val: String = format!("Basic {}", BASE64_ENGINE.encode(auth_id)); Ok(vec![ ( @@ -209,8 +217,8 @@ impl ConnectorIntegration CustomResult { let connector_req = iatapay::IatapayAuthUpdateRequest::try_from(req)?; Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) @@ -218,12 +226,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let req = Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .attach_default_headers() .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) .url(&types::RefreshTokenType::get_url(self, req, connectors)?) @@ -237,10 +245,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: iatapay::IatapayAuthUpdateResponse = res .response .parse_struct("iatapay IatapayAuthUpdateResponse") @@ -249,7 +257,7 @@ impl ConnectorIntegration for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Iatapay".to_string()) .into(), @@ -303,14 +301,12 @@ impl } } -impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -320,18 +316,18 @@ impl ConnectorIntegration CustomResult { Ok(format!("{}/payments/", self.base_url(connectors))) } fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -344,12 +340,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -366,17 +362,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: IatapayPaymentsResponse = res .response .parse_struct("Iatapay PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -393,14 +389,12 @@ impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -410,8 +404,8 @@ impl ConnectorIntegration CustomResult { let auth: iatapay::IatapayAuthType = iatapay::IatapayAuthType::try_from(&req.connector_auth_type)?; @@ -425,12 +419,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -440,17 +434,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: IatapayPaymentsResponse = res .response .parse_struct("iatapay PaymentsSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -467,14 +461,12 @@ impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn build_request( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "Capture".to_string(), connector: "Iatapay".to_string(), @@ -483,19 +475,14 @@ impl ConnectorIntegration - for Iatapay -{ -} +impl ConnectorIntegration for Iatapay {} -impl ConnectorIntegration - for Iatapay -{ +impl ConnectorIntegration for Iatapay { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -505,8 +492,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}{}{}{}", @@ -519,10 +506,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( + let refund_amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -535,11 +522,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -554,17 +541,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: iatapay::RefundResponse = res .response .parse_struct("iatapay RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -581,12 +568,12 @@ impl ConnectorIntegration for Iatapay { +impl ConnectorIntegration for Iatapay { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -596,8 +583,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}{}{}", @@ -612,12 +599,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -627,17 +614,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: iatapay::RefundResponse = res .response .parse_struct("iatapay RefundSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -655,28 +642,27 @@ impl ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::HmacSha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { - let base64_signature = - connector_utils::get_header_key_value("Authorization", request.headers)?; + let base64_signature = get_header_key_value("Authorization", request.headers)?; let base64_signature = base64_signature.replace("IATAPAY-HMAC-SHA256 ", ""); base64_decode(base64_signature) } fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _merchant_id: &common_utils::id_type::MerchantId, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { @@ -687,7 +673,7 @@ impl api::IncomingWebhook for Iatapay { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let notif: iatapay::IatapayWebhookResponse = request .body @@ -729,18 +715,18 @@ impl api::IncomingWebhook for Iatapay { fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let notif: iatapay::IatapayWebhookResponse = request .body .parse_struct("IatapayWebhookResponse") .change_context(errors::ConnectorError::WebhookEventTypeNotFound)?; - api::IncomingWebhookEvent::try_from(notif) + IncomingWebhookEvent::try_from(notif) } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let notif: iatapay::IatapayWebhookResponse = request .body diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs similarity index 68% rename from crates/router/src/connector/iatapay/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs index 641505780b4..702a7a9b19f 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs @@ -1,16 +1,35 @@ use std::collections::HashMap; -use common_utils::{errors::CustomResult, ext_traits::Encode, types::FloatMajorUnit}; +use api_models::webhooks::IncomingWebhookEvent; +use common_enums::enums; +use common_utils::{ + errors::CustomResult, ext_traits::Encode, request::Method, types::FloatMajorUnit, +}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{BankRedirectData, PaymentMethodData, RealTimePaymentData, UpiData}, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + refunds::{Execute, RSync}, + Authorize, + }, + router_request_types::{PaymentsAuthorizeData, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types::{self, RefundsRouterData}, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{Secret, SwitchStrategy}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self as connector_util, PaymentsAuthorizeRequestData, RefundsRequestData}, - consts, - core::errors, - services, - types::{self, api, domain, storage::enums, PaymentsAuthorizeData}, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{ + get_unimplemented_payment_method_error_message, is_payment_failure, is_refund_failure, + PaymentsAuthorizeRequestData, RefundsRequestData, + }, }; type Error = error_stack::Report; @@ -50,15 +69,15 @@ pub struct IatapayAuthUpdateResponse { pub expires_in: i64, } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::AccessToken { + response: Ok(AccessToken { token: item.response.access_token, expires: item.response.expires_in, }), @@ -105,114 +124,97 @@ pub struct IatapayPaymentsRequest { } impl - TryFrom< - &IatapayRouterData< - &types::RouterData< - api::payments::Authorize, - PaymentsAuthorizeData, - types::PaymentsResponseData, - >, - >, - > for IatapayPaymentsRequest + TryFrom<&IatapayRouterData<&RouterData>> + for IatapayPaymentsRequest { type Error = error_stack::Report; fn try_from( item: &IatapayRouterData< - &types::RouterData< - api::payments::Authorize, - PaymentsAuthorizeData, - types::PaymentsResponseData, - >, + &RouterData, >, ) -> Result { let return_url = item.router_data.request.get_router_return_url()?; // Iatapay processes transactions through the payment method selected based on the country - let (country, payer_info, preferred_checkout_method) = - match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Upi(upi_type) => match upi_type { - domain::UpiData::UpiCollect(upi_data) => ( - common_enums::CountryAlpha2::IN, - upi_data.vpa_id.map(|id| PayerInfo { - token_id: id.switch_strategy(), - }), - None, - ), - domain::UpiData::UpiIntent(_) => ( - common_enums::CountryAlpha2::IN, - None, - Some(PreferredCheckoutMethod::Qr), - ), - }, - domain::PaymentMethodData::BankRedirect(bank_redirect_data) => { - match bank_redirect_data { - domain::BankRedirectData::Ideal { .. } => { - (common_enums::CountryAlpha2::NL, None, None) - } - domain::BankRedirectData::LocalBankRedirect {} => { - (common_enums::CountryAlpha2::AT, None, None) - } - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Bizum {} - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Eps { .. } - | domain::BankRedirectData::Giropay { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Sofort { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } => { - Err(errors::ConnectorError::NotImplemented( - connector_util::get_unimplemented_payment_method_error_message( - "iatapay", - ), - ))? - } - } - } - domain::PaymentMethodData::RealTimePayment(real_time_payment_data) => { - match *real_time_payment_data { - domain::RealTimePaymentData::DuitNow {} => { - (common_enums::CountryAlpha2::MY, None, None) - } - domain::RealTimePaymentData::Fps {} => { - (common_enums::CountryAlpha2::HK, None, None) - } - domain::RealTimePaymentData::PromptPay {} => { - (common_enums::CountryAlpha2::TH, None, None) - } - domain::RealTimePaymentData::VietQr {} => { - (common_enums::CountryAlpha2::VN, None, None) - } - } + let (country, payer_info, preferred_checkout_method) = match item + .router_data + .request + .payment_method_data + .clone() + { + PaymentMethodData::Upi(upi_type) => match upi_type { + UpiData::UpiCollect(upi_data) => ( + common_enums::CountryAlpha2::IN, + upi_data.vpa_id.map(|id| PayerInfo { + token_id: id.switch_strategy(), + }), + None, + ), + UpiData::UpiIntent(_) => ( + common_enums::CountryAlpha2::IN, + None, + Some(PreferredCheckoutMethod::Qr), + ), + }, + PaymentMethodData::BankRedirect(bank_redirect_data) => match bank_redirect_data { + BankRedirectData::Ideal { .. } => (common_enums::CountryAlpha2::NL, None, None), + BankRedirectData::LocalBankRedirect {} => { + (common_enums::CountryAlpha2::AT, None, None) } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Giropay { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } => { Err(errors::ConnectorError::NotImplemented( - connector_util::get_unimplemented_payment_method_error_message("iatapay"), + get_unimplemented_payment_method_error_message("iatapay"), ))? } - }; + }, + PaymentMethodData::RealTimePayment(real_time_payment_data) => { + match *real_time_payment_data { + RealTimePaymentData::DuitNow {} => { + (common_enums::CountryAlpha2::MY, None, None) + } + RealTimePaymentData::Fps {} => (common_enums::CountryAlpha2::HK, None, None), + RealTimePaymentData::PromptPay {} => { + (common_enums::CountryAlpha2::TH, None, None) + } + RealTimePaymentData::VietQr {} => (common_enums::CountryAlpha2::VN, None, None), + } + } + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("iatapay"), + ))? + } + }; let payload = Self { merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)? .merchant_id, @@ -244,11 +246,11 @@ pub struct IatapayAuthType { pub(super) client_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for IatapayAuthType { +impl TryFrom<&ConnectorAuthType> for IatapayAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::SignatureKey { + ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -321,21 +323,21 @@ fn get_iatpay_response( ) -> CustomResult< ( enums::AttemptStatus, - Option, - types::PaymentsResponseData, + Option, + PaymentsResponseData, ), errors::ConnectorError, > { let status = enums::AttemptStatus::from(response.status); - let error = if connector_util::is_payment_failure(status) { - Some(types::ErrorResponse { + let error = if is_payment_failure(status) { + Some(ErrorResponse { code: response .failure_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: response .failure_details .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: response.failure_details, status_code, attempt_status: Some(status), @@ -346,8 +348,8 @@ fn get_iatpay_response( }; let form_fields = HashMap::new(); let id = match response.iata_payment_id.clone() { - Some(s) => types::ResponseId::ConnectorTransactionId(s), - None => types::ResponseId::NoResponseId, + Some(s) => ResponseId::ConnectorTransactionId(s), + None => ResponseId::NoResponseId, }; let connector_response_reference_id = response.merchant_payment_id.or(response.iata_payment_id); @@ -371,15 +373,15 @@ fn get_iatpay_response( } false => ( None, - Some(services::RedirectForm::Form { + Some(RedirectForm::Form { endpoint: checkout_methods.redirect.redirect_url, - method: services::Method::Get, + method: Method::Get, form_fields, }), ), }; - types::PaymentsResponseData::TransactionResponse { + PaymentsResponseData::TransactionResponse { resource_id: id, redirection_data: Box::new(redirection_data), mandate_reference: Box::new(None), @@ -390,7 +392,7 @@ fn get_iatpay_response( charge_id: None, } } - None => types::PaymentsResponseData::TransactionResponse { + None => PaymentsResponseData::TransactionResponse { resource_id: id.clone(), redirection_data: Box::new(None), mandate_reference: Box::new(None), @@ -405,13 +407,12 @@ fn get_iatpay_response( Ok((status, error, payment_response_data)) } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = Error; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let (status, error, payment_response_data) = get_iatpay_response(item.response, item.http_code)?; @@ -436,11 +437,9 @@ pub struct IatapayRefundRequest { pub notification_url: String, } -impl TryFrom<&IatapayRouterData<&types::RefundsRouterData>> for IatapayRefundRequest { +impl TryFrom<&IatapayRouterData<&RefundsRouterData>> for IatapayRefundRequest { type Error = error_stack::Report; - fn try_from( - item: &IatapayRouterData<&types::RefundsRouterData>, - ) -> Result { + fn try_from(item: &IatapayRouterData<&RefundsRouterData>) -> Result { Ok(Self { amount: item.amount, merchant_id: IatapayAuthType::try_from(&item.router_data.connector_auth_type)? @@ -506,32 +505,30 @@ pub struct RefundResponse { account_country: Option, } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); - let response = if connector_util::is_refund_failure(refund_status) { - Err(types::ErrorResponse { + let response = if is_refund_failure(refund_status) { + Err(ErrorResponse { code: item .response .failure_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: item .response .failure_details .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: item.response.failure_details, status_code: item.http_code, attempt_status: None, connector_transaction_id: Some(item.response.iata_refund_id.clone()), }) } else { - Ok(types::RefundsResponseData { + Ok(RefundsResponseData { connector_refund_id: item.response.iata_refund_id.to_string(), refund_status, }) @@ -544,32 +541,30 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { let refund_status = enums::RefundStatus::from(item.response.status); - let response = if connector_util::is_refund_failure(refund_status) { - Err(types::ErrorResponse { + let response = if is_refund_failure(refund_status) { + Err(ErrorResponse { code: item .response .failure_code - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: item .response .failure_details .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: item.response.failure_details, status_code: item.http_code, attempt_status: None, connector_transaction_id: Some(item.response.iata_refund_id.clone()), }) } else { - Ok(types::RefundsResponseData { + Ok(RefundsResponseData { connector_refund_id: item.response.iata_refund_id.to_string(), refund_status, }) @@ -627,7 +622,7 @@ pub enum IatapayWebhookResponse { IatapayRefundWebhookBody(IatapayRefundWebhookBody), } -impl TryFrom for api::IncomingWebhookEvent { +impl TryFrom for IncomingWebhookEvent { type Error = error_stack::Report; fn try_from(payload: IatapayWebhookResponse) -> CustomResult { match payload { diff --git a/crates/router/src/connector/itaubank.rs b/crates/hyperswitch_connectors/src/connectors/itaubank.rs similarity index 71% rename from crates/router/src/connector/itaubank.rs rename to crates/hyperswitch_connectors/src/connectors/itaubank.rs index 2087c72ba85..34956ace644 100644 --- a/crates/router/src/connector/itaubank.rs +++ b/crates/hyperswitch_connectors/src/connectors/itaubank.rs @@ -2,25 +2,51 @@ pub mod transformers; use std::fmt::Write; -use common_utils::types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}; +use api_models::webhooks::{IncomingWebhookEvent, ObjectReferenceId}; +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, +}; use error_stack::{report, ResultExt}; -use hyperswitch_interfaces::consts; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::{ACCEPT_HEADER, NO_ERROR_CODE, NO_ERROR_MESSAGE, USER_AGENT}, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; use masking::PeekInterface; use transformers as itaubank; -use super::utils::{self as connector_utils, RefundsRequestData}; use crate::{ - configs::settings, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - ErrorResponse, RequestContent, Response, - }, - utils::BytesExt, + constants::headers, + types::{RefreshTokenRouterData, ResponseRouterData}, + utils::{convert_amount, RefundsRequestData}, }; #[derive(Clone)] @@ -49,12 +75,8 @@ impl api::RefundExecute for Itaubank {} impl api::RefundSync for Itaubank {} impl api::PaymentToken for Itaubank {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Itaubank +impl ConnectorIntegration + for Itaubank { } @@ -64,9 +86,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let access_token = req.access_token .clone() @@ -81,11 +103,11 @@ where ), ( headers::ACCEPT.to_string(), - consts::ACCEPT_HEADER.to_string().into(), + ACCEPT_HEADER.to_string().into(), ), ( headers::USER_AGENT.to_string(), - consts::USER_AGENT.to_string().into(), + USER_AGENT.to_string().into(), ), ( headers::CONTENT_TYPE.to_string(), @@ -110,7 +132,7 @@ impl ConnectorCommon for Itaubank { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.itaubank.base_url.as_ref() } @@ -146,14 +168,11 @@ impl ConnectorCommon for Itaubank { Ok(ErrorResponse { status_code: res.status_code, - code: response - .error - .title - .unwrap_or(consts::NO_ERROR_CODE.to_string()), + code: response.error.title.unwrap_or(NO_ERROR_CODE.to_string()), message: response .error .detail - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason, attempt_status: None, connector_transaction_id: None, @@ -163,18 +182,13 @@ impl ConnectorCommon for Itaubank { impl ConnectorValidation for Itaubank {} -impl ConnectorIntegration - for Itaubank -{ -} +impl ConnectorIntegration for Itaubank {} -impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn get_url( &self, - _req: &types::RefreshTokenRouterData, - connectors: &settings::Connectors, + _req: &RefreshTokenRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!("{}api/oauth/jwt", self.base_url(connectors))) } @@ -183,9 +197,9 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let flow_header = vec![ ( headers::CONTENT_TYPE.to_string(), @@ -195,19 +209,19 @@ impl ConnectorIntegration CustomResult { let connector_req = itaubank::ItaubankAuthRequest::try_from(req)?; @@ -216,13 +230,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; let req = Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .attach_default_headers() .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) .url(&types::RefreshTokenType::get_url(self, req, connectors)?) @@ -239,10 +253,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: itaubank::ItaubankUpdateTokenResponse = res .response .parse_struct("ItaubankUpdateTokenResponse") @@ -251,7 +265,7 @@ impl ConnectorIntegration for Itaubank +impl ConnectorIntegration + for Itaubank { fn build_request( &self, - _req: &types::SetupMandateRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "setup mandate".to_string(), connector: "itaubank".to_string(), @@ -306,14 +316,12 @@ impl } } -impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -323,8 +331,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}itau-ep9-gtw-pix-recebimentos-ext-v2/v2/cob", @@ -334,10 +342,10 @@ impl ConnectorIntegration CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -350,13 +358,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -375,17 +383,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: itaubank::ItaubankPaymentsResponse = res .response .parse_struct("Itaubank PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -401,14 +409,12 @@ impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -418,8 +424,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}itau-ep9-gtw-pix-recebimentos-ext-v2/v2/cob/{}", @@ -433,13 +439,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -451,17 +457,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: itaubank::ItaubankPaymentsSyncResponse = res .response .parse_struct("itaubank PaymentsSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -477,14 +483,12 @@ impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -494,29 +498,29 @@ impl ConnectorIntegration CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -533,17 +537,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: itaubank::ItaubankPaymentsResponse = res .response .parse_struct("Itaubank PaymentsCaptureResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -559,14 +563,12 @@ impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn build_request( &self, - _req: &types::PaymentsCancelRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "void".to_string(), connector: "itaubank".to_string(), @@ -575,14 +577,12 @@ impl ConnectorIntegration - for Itaubank -{ +impl ConnectorIntegration for Itaubank { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -592,8 +592,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let itaubank_metadata = req.request.get_connector_metadata()?; let pix_data: itaubank::ItaubankMetaData = serde_json::from_value(itaubank_metadata) @@ -614,10 +614,10 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let refund_amount = connector_utils::convert_amount( + let refund_amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -630,12 +630,12 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; - let request = services::RequestBuilder::new() - .method(services::Method::Put) + let request = RequestBuilder::new() + .method(Method::Put) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -652,17 +652,17 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: itaubank::RefundResponse = res .response .parse_struct("itaubank RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -678,12 +678,12 @@ impl ConnectorIntegration for Itaubank { +impl ConnectorIntegration for Itaubank { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -693,8 +693,8 @@ impl ConnectorIntegration CustomResult { let itaubank_metadata = req.request.get_connector_metadata()?; let pix_data: itaubank::ItaubankMetaData = serde_json::from_value(itaubank_metadata) @@ -715,13 +715,13 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { let auth_details = itaubank::ItaubankAuthType::try_from(&req.connector_auth_type)?; Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundSyncType::get_headers(self, req, connectors)?) @@ -736,17 +736,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: itaubank::RefundResponse = res .response .parse_struct("itaubank RefundResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -763,24 +763,24 @@ impl ConnectorIntegration, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/itaubank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/itaubank/transformers.rs similarity index 71% rename from crates/router/src/connector/itaubank/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/itaubank/transformers.rs index 127f8526afe..c5d66d9a156 100644 --- a/crates/router/src/connector/itaubank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/itaubank/transformers.rs @@ -1,16 +1,24 @@ -use api_models::payments; -use common_utils::{ext_traits::Encode, types::StringMajorUnit}; +use api_models::payments::QrCodeInformation; +use common_enums::enums; +use common_utils::{errors::CustomResult, ext_traits::Encode, types::StringMajorUnit}; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{BankTransferData, PaymentMethodData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types, +}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; use url::Url; use crate::{ - connector::utils::{self, RouterData}, - core::errors, - types::{self, api, domain, storage::enums}, - utils as crate_utils, + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{get_timestamp_in_milliseconds, QrImage, RouterData as _}, }; pub struct ItaubankRouterData { @@ -55,9 +63,9 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub item: &ItaubankRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::BankTransfer(bank_transfer_data) => { + PaymentMethodData::BankTransfer(bank_transfer_data) => { match *bank_transfer_data { - domain::BankTransferData::Pix { pix_key, cpf, cnpj } => { + BankTransferData::Pix { pix_key, cpf, cnpj } => { let nome = item.router_data.get_optional_billing_full_name(); // cpf and cnpj are mutually exclusive let devedor = match (cnpj, cpf) { @@ -85,19 +93,19 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub devedor, }) } - domain::BankTransferData::AchBankTransfer {} - | domain::BankTransferData::SepaBankTransfer {} - | domain::BankTransferData::BacsBankTransfer {} - | domain::BankTransferData::MultibancoBankTransfer {} - | domain::BankTransferData::PermataBankTransfer {} - | domain::BankTransferData::BcaBankTransfer {} - | domain::BankTransferData::BniVaBankTransfer {} - | domain::BankTransferData::BriVaBankTransfer {} - | domain::BankTransferData::CimbVaBankTransfer {} - | domain::BankTransferData::DanamonVaBankTransfer {} - | domain::BankTransferData::MandiriVaBankTransfer {} - | domain::BankTransferData::Pse {} - | domain::BankTransferData::LocalBankTransfer { .. } => { + BankTransferData::AchBankTransfer {} + | BankTransferData::SepaBankTransfer {} + | BankTransferData::BacsBankTransfer {} + | BankTransferData::MultibancoBankTransfer {} + | BankTransferData::PermataBankTransfer {} + | BankTransferData::BcaBankTransfer {} + | BankTransferData::BniVaBankTransfer {} + | BankTransferData::BriVaBankTransfer {} + | BankTransferData::CimbVaBankTransfer {} + | BankTransferData::DanamonVaBankTransfer {} + | BankTransferData::MandiriVaBankTransfer {} + | BankTransferData::Pse {} + | BankTransferData::LocalBankTransfer { .. } => { Err(errors::ConnectorError::NotImplemented( "Selected payment method through itaubank".to_string(), ) @@ -105,24 +113,24 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub } } } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( "Selected payment method through itaubank".to_string(), ) @@ -139,11 +147,11 @@ pub struct ItaubankAuthType { pub(super) certificate_key: Option>, } -impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType { +impl TryFrom<&ConnectorAuthType> for ItaubankAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::MultiAuthKey { + ConnectorAuthType::MultiAuthKey { api_key, key1, api_secret, @@ -154,7 +162,7 @@ impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType { certificate: Some(api_secret.to_owned()), certificate_key: Some(key2.to_owned()), }), - types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { client_secret: api_key.to_owned(), client_id: key1.to_owned(), certificate: None, @@ -206,15 +214,15 @@ pub struct ItaubankTokenErrorResponse { pub user_message: Option, } -impl TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::AccessToken { + response: Ok(AccessToken { token: item.response.access_token, expires: item.response.expires_in, }), @@ -261,26 +269,18 @@ pub struct ItaubankPixExpireTime { expiracao: i64, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - ItaubankPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let connector_metadata = get_qr_code_data(&item.response)?; Ok(Self { status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.txid.to_owned(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.txid.to_owned()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata, @@ -296,18 +296,18 @@ impl fn get_qr_code_data( response: &ItaubankPaymentsResponse, -) -> errors::CustomResult, errors::ConnectorError> { - let creation_time = utils::get_timestamp_in_milliseconds(&response.calendario.criacao); +) -> CustomResult, errors::ConnectorError> { + let creation_time = get_timestamp_in_milliseconds(&response.calendario.criacao); // convert expiration to milliseconds and add to creation time let expiration_time = creation_time + (response.calendario.expiracao * 1000); - let image_data = crate_utils::QrImage::new_from_data(response.pix_qr_value.clone()) + let image_data = QrImage::new_from_data(response.pix_qr_value.clone()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; let image_data_url = Url::parse(image_data.data.clone().as_str()) .change_context(errors::ConnectorError::ResponseHandlingFailed)?; - let qr_code_info = payments::QrCodeInformation::QrDataUrl { + let qr_code_info = QrCodeInformation::QrDataUrl { image_data_url, display_to_timestamp: Some(expiration_time), }; @@ -336,19 +336,12 @@ pub struct ItaubankMetaData { pub pix_id: Option, } -impl - TryFrom< - types::ResponseRouterData, - > for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - ItaubankPaymentsSyncResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let pix_data = item .response @@ -365,10 +358,8 @@ impl Ok(Self { status: enums::AttemptStatus::from(item.response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - item.response.txid.to_owned(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.txid.to_owned()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata, @@ -424,15 +415,15 @@ pub struct RefundResponse { status: RefundStatus, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.rtr_id, refund_status: enums::RefundStatus::from(item.response.status), }), @@ -441,15 +432,13 @@ impl TryFrom> } } -impl TryFrom> - for types::RefundsRouterData -{ +impl TryFrom> for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { - response: Ok(types::RefundsResponseData { + response: Ok(RefundsResponseData { connector_refund_id: item.response.rtr_id.to_string(), refund_status: enums::RefundStatus::from(item.response.status), }), diff --git a/crates/router/src/connector/klarna.rs b/crates/hyperswitch_connectors/src/connectors/klarna.rs similarity index 78% rename from crates/router/src/connector/klarna.rs rename to crates/hyperswitch_connectors/src/connectors/klarna.rs index 9b0da0ade6d..dd0110d9593 100644 --- a/crates/router/src/connector/klarna.rs +++ b/crates/hyperswitch_connectors/src/connectors/klarna.rs @@ -1,34 +1,58 @@ pub mod transformers; -use api_models::enums; +use api_models::webhooks::IncomingWebhookEvent; use base64::Engine; +use common_enums::enums; use common_utils::{ - request::RequestContent, + consts::BASE64_ENGINE, + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; -use masking::PeekInterface; +use hyperswitch_domain_models::{ + payment_method_data::{PayLaterData, PaymentMethodData}, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSessionRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + consts::NO_ERROR_MESSAGE, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; +use masking::{Mask, PeekInterface}; use router_env::logger; use transformers as klarna; use crate::{ - configs::settings, - connector::{utils as connector_utils, utils::RefundsRequestData}, - consts, - core::errors::{self, CustomResult}, - events::connector_api_logs::ConnectorEvent, - headers, - services::{ - self, - request::{self, Mask}, - ConnectorSpecifications, ConnectorValidation, + constants::headers, + types::ResponseRouterData, + utils::{ + construct_not_supported_error_report, convert_amount, get_http_header, + get_unimplemented_payment_method_error_message, missing_field_err, RefundsRequestData, }, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - domain, ErrorResponse, Response, - }, - utils::BytesExt, }; #[derive(Clone)] @@ -57,21 +81,18 @@ impl ConnectorCommon for Klarna { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.klarna.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = klarna::KlarnaAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - let encoded_api_key = consts::BASE64_ENGINE.encode(format!( - "{}:{}", - auth.username.peek(), - auth.password.peek() - )); + let encoded_api_key = + BASE64_ENGINE.encode(format!("{}:{}", auth.username.peek(), auth.password.peek())); Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -101,7 +122,7 @@ impl ConnectorCommon for Klarna { code: response.error_code, message: response .error_message - .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or(NO_ERROR_MESSAGE.to_string()), reason, attempt_status: None, connector_transaction_id: None, @@ -122,7 +143,7 @@ impl ConnectorValidation for Klarna { | enums::CaptureMethod::Manual | enums::CaptureMethod::SequentialAutomatic => Ok(()), enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( - connector_utils::construct_not_supported_error_report(capture_method, self.id()), + construct_not_supported_error_report(capture_method, self.id()), ), } } @@ -138,35 +159,25 @@ impl api::PaymentSession for Klarna {} impl api::ConnectorAccessToken for Klarna {} impl api::PaymentToken for Klarna {} -impl - services::ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Klarna +impl ConnectorIntegration + for Klarna { // Not Implemented (R) } -impl - services::ConnectorIntegration< - api::AccessTokenAuth, - types::AccessTokenRequestData, - types::AccessToken, - > for Klarna -{ +impl ConnectorIntegration for Klarna { // Not Implemented (R) } impl ConnectorCommonExt for Klarna where - Self: services::ConnectorIntegration, + Self: ConnectorIntegration, { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), types::PaymentsAuthorizeType::get_content_type(self) @@ -195,18 +206,12 @@ fn build_region_specific_endpoint( Ok(base_url.replace("{{klarna_region}}", &klarna_region)) } -impl - services::ConnectorIntegration< - api::Session, - types::PaymentsSessionData, - types::PaymentsResponseData, - > for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSessionRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -216,8 +221,8 @@ impl fn get_url( &self, - req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, + req: &PaymentsSessionRouterData, + connectors: &Connectors, ) -> CustomResult { let endpoint = build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?; @@ -227,10 +232,10 @@ impl fn get_request_body( &self, - req: &types::PaymentsSessionRouterData, - _connectors: &settings::Connectors, + req: &PaymentsSessionRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -244,12 +249,12 @@ impl fn build_request( &self, - req: &types::PaymentsSessionRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSessionRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSessionType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSessionType::get_headers( @@ -264,10 +269,10 @@ impl fn handle_response( &self, - data: &types::PaymentsSessionRouterData, + data: &PaymentsSessionRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: klarna::KlarnaSessionResponse = res .response .parse_struct("KlarnaSessionResponse") @@ -276,7 +281,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -295,23 +300,13 @@ impl impl api::MandateSetup for Klarna {} -impl - services::ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Klarna -{ +impl ConnectorIntegration for Klarna { // Not Implemented(R) fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Klarna".to_string()) .into(), @@ -319,18 +314,12 @@ impl } } -impl - services::ConnectorIntegration< - api::Capture, - types::PaymentsCaptureData, - types::PaymentsResponseData, - > for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -340,8 +329,8 @@ impl fn get_url( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { let order_id = req.request.connector_transaction_id.clone(); let endpoint = @@ -354,10 +343,10 @@ impl fn get_request_body( &self, - req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount_to_capture, req.request.currency, @@ -369,11 +358,11 @@ impl fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -388,13 +377,13 @@ impl fn handle_response( &self, - data: &types::PaymentsCaptureRouterData, + data: &PaymentsCaptureRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { match res.headers { Some(headers) => { - let capture_id = connector_utils::get_http_header("Capture-Id", &headers) + let capture_id = get_http_header("Capture-Id", &headers) .attach_printable("Missing capture id in headers") .change_context(errors::ConnectorError::ResponseHandlingFailed)?; let response = klarna::KlarnaCaptureResponse { @@ -403,7 +392,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -424,22 +413,19 @@ impl } } -impl - services::ConnectorIntegration - for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { let order_id = req .request @@ -471,12 +457,12 @@ impl fn build_request( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -486,10 +472,10 @@ impl fn handle_response( &self, - data: &types::PaymentsSyncRouterData, + data: &PaymentsSyncRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: klarna::KlarnaPsyncResponse = res .response .parse_struct("klarna KlarnaPsyncResponse") @@ -498,7 +484,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -514,18 +500,12 @@ impl } } -impl - services::ConnectorIntegration< - api::Authorize, - types::PaymentsAuthorizeData, - types::PaymentsResponseData, - > for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -535,25 +515,25 @@ impl fn get_url( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { let payment_method_data = &req.request.payment_method_data; let payment_experience = req .request .payment_experience .as_ref() - .ok_or_else(connector_utils::missing_field_err("payment_experience"))?; + .ok_or_else(missing_field_err("payment_experience"))?; let payment_method_type = req .request .payment_method_type .as_ref() - .ok_or_else(connector_utils::missing_field_err("payment_method_type"))?; + .ok_or_else(missing_field_err("payment_method_type"))?; let endpoint = build_region_specific_endpoint(self.base_url(connectors), &req.connector_meta_data)?; match payment_method_data { - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaSdk { token }) => { + PaymentMethodData::PayLater(PayLaterData::KlarnaSdk { token }) => { match (payment_experience, payment_method_type) { ( common_enums::PaymentExperience::InvokeSdkClient, @@ -672,7 +652,7 @@ impl })), } } - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaRedirect {}) => { + PaymentMethodData::PayLater(PayLaterData::KlarnaRedirect {}) => { match (payment_experience, payment_method_type) { ( common_enums::PaymentExperience::RedirectToUrl, @@ -790,29 +770,27 @@ impl } } - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::Wallet(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(report!(errors::ConnectorError::NotImplemented( - connector_utils::get_unimplemented_payment_method_error_message( - req.connector.as_str(), - ), + get_unimplemented_payment_method_error_message(req.connector.as_str(),), ))) } } @@ -820,10 +798,10 @@ impl fn get_request_body( &self, - req: &types::PaymentsAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_amount, req.request.currency, @@ -836,12 +814,12 @@ impl fn build_request( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -858,10 +836,10 @@ impl fn handle_response( &self, - data: &types::PaymentsAuthorizeRouterData, + data: &PaymentsAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: klarna::KlarnaAuthResponse = res .response .parse_struct("KlarnaPaymentsResponse") @@ -870,7 +848,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -887,25 +865,19 @@ impl } } -impl - services::ConnectorIntegration< - api::Void, - types::PaymentsCancelData, - types::PaymentsResponseData, - > for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_url( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, + req: &PaymentsCancelRouterData, + connectors: &Connectors, ) -> CustomResult { let order_id = req.request.connector_transaction_id.clone(); let endpoint = @@ -918,12 +890,12 @@ impl fn build_request( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -936,10 +908,10 @@ impl fn handle_response( &self, - data: &types::PaymentsCancelRouterData, + data: &PaymentsCancelRouterData, _event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { logger::debug!("Expected zero bytes response, skipped parsing of the response"); let status = if res.status_code == 204 { @@ -947,7 +919,7 @@ impl } else { enums::AttemptStatus::VoidFailed }; - Ok(types::PaymentsCancelRouterData { + Ok(PaymentsCancelRouterData { status, ..data.clone() }) @@ -966,14 +938,12 @@ impl api::Refund for Klarna {} impl api::RefundExecute for Klarna {} impl api::RefundSync for Klarna {} -impl services::ConnectorIntegration - for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -983,8 +953,8 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, + req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { let order_id = req.request.connector_transaction_id.clone(); let endpoint = @@ -997,10 +967,10 @@ impl services::ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { - let amount = connector_utils::convert_amount( + let amount = convert_amount( self.amount_converter, req.request.minor_refund_amount, req.request.currency, @@ -1012,11 +982,11 @@ impl services::ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -1031,13 +1001,13 @@ impl services::ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { match res.headers { Some(headers) => { - let refund_id = connector_utils::get_http_header("Refund-Id", &headers) + let refund_id = get_http_header("Refund-Id", &headers) .attach_printable("Missing refund id in headers") .change_context(errors::ConnectorError::ResponseHandlingFailed)?; let response = klarna::KlarnaRefundResponse { @@ -1046,7 +1016,7 @@ impl services::ConnectorIntegration - for Klarna -{ +impl ConnectorIntegration for Klarna { fn get_headers( &self, - req: &types::RefundSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -1083,8 +1051,8 @@ impl services::ConnectorIntegration CustomResult { let order_id = req.request.connector_transaction_id.clone(); let refund_id = req.request.get_connector_refund_id()?; @@ -1098,12 +1066,12 @@ impl services::ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::RefundSyncType::get_url(self, req, connectors)?) .headers(types::RefundSyncType::get_headers(self, req, connectors)?) .build(), @@ -1112,17 +1080,17 @@ impl services::ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: klarna::KlarnaRefundSyncResponse = res .response .parse_struct("klarna KlarnaRefundSyncResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -1139,24 +1107,24 @@ impl services::ConnectorIntegration, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { - Ok(api::IncomingWebhookEvent::EventNotSupported) + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Ok(IncomingWebhookEvent::EventNotSupported) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/klarna/transformers.rs b/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs similarity index 81% rename from crates/router/src/connector/klarna/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs index d1a0b80bad6..28584c2a7c4 100644 --- a/crates/router/src/connector/klarna/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs @@ -1,18 +1,28 @@ -use api_models::payments; +use api_models::payments::{KlarnaSessionTokenResponse, SessionToken}; +use common_enums::enums; use common_utils::{pii, types::MinorUnit}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ - router_data::KlarnaSdkResponse, router_response_types::RedirectForm, + payment_method_data::{PayLaterData, PaymentMethodData}, + router_data::{ + AdditionalPaymentMethodConnectorResponse, ConnectorAuthType, ConnectorResponseData, + KlarnaSdkResponse, RouterData, + }, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::{PaymentsCaptureData, ResponseId}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + types, }; +use hyperswitch_interfaces::errors; use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{ - self, AddressData, AddressDetailsData, PaymentsAuthorizeRequestData, RouterData, + types::{ + PaymentsResponseRouterData, PaymentsSessionResponseRouterData, RefundsResponseRouterData, + ResponseRouterData, }, - core::errors, - types::{self, api, domain, storage::enums, transformers::ForeignFrom}, + utils::{self, AddressData, AddressDetailsData, PaymentsAuthorizeRequestData, RouterData as _}, }; #[derive(Debug, Serialize)] @@ -125,7 +135,7 @@ pub struct AuthorizedPaymentMethod { payment_type: String, } -impl From for types::AdditionalPaymentMethodConnectorResponse { +impl From for AdditionalPaymentMethodConnectorResponse { fn from(item: AuthorizedPaymentMethod) -> Self { Self::PayLater { klarna_sdk: Some(KlarnaSdkResponse { @@ -206,22 +216,20 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsSessionRouterData>> for KlarnaSes } } -impl TryFrom> +impl TryFrom> for types::PaymentsSessionRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsSessionResponseRouterData, + item: PaymentsSessionResponseRouterData, ) -> Result { let response = &item.response; Ok(Self { - response: Ok(types::PaymentsResponseData::SessionResponse { - session_token: api::SessionToken::Klarna(Box::new( - payments::KlarnaSessionTokenResponse { - session_token: response.client_token.clone().expose(), - session_id: response.session_id.clone(), - }, - )), + response: Ok(PaymentsResponseData::SessionResponse { + session_token: SessionToken::Klarna(Box::new(KlarnaSessionTokenResponse { + session_token: response.client_token.clone().expose(), + session_id: response.session_id.clone(), + })), }), ..item.data }) @@ -239,7 +247,7 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP let return_url = item.router_data.request.get_router_return_url()?; let webhook_url = item.router_data.request.get_webhook_url()?; match payment_method_data { - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaSdk { .. }) => { + PaymentMethodData::PayLater(PayLaterData::KlarnaSdk { .. }) => { match request.order_details.clone() { Some(order_details) => Ok(Self { purchase_country: item.router_data.get_billing_country()?, @@ -277,7 +285,7 @@ impl TryFrom<&KlarnaRouterData<&types::PaymentsAuthorizeRouterData>> for KlarnaP })), } } - domain::PaymentMethodData::PayLater(domain::PayLaterData::KlarnaRedirect {}) => { + PaymentMethodData::PayLater(PayLaterData::KlarnaRedirect {}) => { match request.order_details.clone() { Some(order_details) => Ok(Self { purchase_country: item.router_data.get_billing_country()?, @@ -356,14 +364,12 @@ fn get_address_info( }) } -impl TryFrom> +impl TryFrom> for types::PaymentsAuthorizeRouterData { type Error = error_stack::Report; - fn try_from( - item: types::PaymentsResponseRouterData, - ) -> Result { + fn try_from(item: PaymentsResponseRouterData) -> Result { match item.response { KlarnaAuthResponse::KlarnaPaymentsAuthResponse(ref response) => { let connector_response = @@ -371,18 +377,16 @@ impl TryFrom> .authorized_payment_method .as_ref() .map(|authorized_payment_method| { - types::ConnectorResponseData::with_additional_payment_method_data( - types::AdditionalPaymentMethodConnectorResponse::from( + ConnectorResponseData::with_additional_payment_method_data( + AdditionalPaymentMethodConnectorResponse::from( authorized_payment_method.clone(), ), ) }); Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.order_id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.order_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -391,19 +395,17 @@ impl TryFrom> incremental_authorization_allowed: None, charge_id: None, }), - status: enums::AttemptStatus::foreign_from(( + status: get_fraud_status( response.fraud_status.clone(), item.data.request.is_auto_capture()?, - )), + ), connector_response, ..item.data }) } KlarnaAuthResponse::KlarnaCheckoutAuthResponse(ref response) => Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.order_id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.order_id.clone()), redirection_data: Box::new(Some(RedirectForm::Html { html_data: response.html_snippet.clone(), })), @@ -414,10 +416,10 @@ impl TryFrom> incremental_authorization_allowed: None, charge_id: None, }), - status: enums::AttemptStatus::foreign_from(( + status: get_checkout_status( response.status.clone(), item.data.request.is_auto_capture()?, - )), + ), connector_response: None, ..item.data }), @@ -448,10 +450,10 @@ pub struct KlarnaAuthType { pub password: Secret, } -impl TryFrom<&types::ConnectorAuthType> for KlarnaAuthType { +impl TryFrom<&ConnectorAuthType> for KlarnaAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::BodyKey { api_key, key1 } = auth_type { Ok(Self { username: key1.to_owned(), password: api_key.to_owned(), @@ -477,34 +479,36 @@ pub enum KlarnaCheckoutStatus { CheckoutIncomplete, } -impl ForeignFrom<(KlarnaFraudStatus, bool)> for enums::AttemptStatus { - fn foreign_from((klarna_status, is_auto_capture): (KlarnaFraudStatus, bool)) -> Self { - match klarna_status { - KlarnaFraudStatus::Accepted => { - if is_auto_capture { - Self::Charged - } else { - Self::Authorized - } +fn get_fraud_status( + klarna_status: KlarnaFraudStatus, + is_auto_capture: bool, +) -> common_enums::AttemptStatus { + match klarna_status { + KlarnaFraudStatus::Accepted => { + if is_auto_capture { + common_enums::AttemptStatus::Charged + } else { + common_enums::AttemptStatus::Authorized } - KlarnaFraudStatus::Pending => Self::Pending, - KlarnaFraudStatus::Rejected => Self::Failure, } + KlarnaFraudStatus::Pending => common_enums::AttemptStatus::Pending, + KlarnaFraudStatus::Rejected => common_enums::AttemptStatus::Failure, } } -impl ForeignFrom<(KlarnaCheckoutStatus, bool)> for enums::AttemptStatus { - fn foreign_from((klarna_status, is_auto_capture): (KlarnaCheckoutStatus, bool)) -> Self { - match klarna_status { - KlarnaCheckoutStatus::CheckoutIncomplete => { - if is_auto_capture { - Self::AuthenticationPending - } else { - Self::Authorized - } +fn get_checkout_status( + klarna_status: KlarnaCheckoutStatus, + is_auto_capture: bool, +) -> common_enums::AttemptStatus { + match klarna_status { + KlarnaCheckoutStatus::CheckoutIncomplete => { + if is_auto_capture { + common_enums::AttemptStatus::AuthenticationPending + } else { + common_enums::AttemptStatus::Authorized } - KlarnaCheckoutStatus::CheckoutComplete => Self::Charged, } + KlarnaCheckoutStatus::CheckoutComplete => common_enums::AttemptStatus::Charged, } } @@ -552,21 +556,18 @@ impl From for enums::AttemptStatus { } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { match item.response { KlarnaPsyncResponse::KlarnaSDKPsyncResponse(response) => Ok(Self { status: enums::AttemptStatus::from(response.status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.order_id.clone(), - ), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.order_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -580,14 +581,9 @@ impl ..item.data }), KlarnaPsyncResponse::KlarnaCheckoutPSyncResponse(response) => Ok(Self { - status: enums::AttemptStatus::foreign_from(( - response.status.clone(), - response.options.auto_capture, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - response.order_id.clone(), - ), + status: get_checkout_status(response.status.clone(), response.options.auto_capture), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(response.order_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -632,22 +628,16 @@ pub struct KlarnaCaptureResponse { } impl - TryFrom< - types::ResponseRouterData< - F, - KlarnaCaptureResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, - >, - > for types::RouterData + TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< + item: ResponseRouterData< F, KlarnaCaptureResponse, - types::PaymentsCaptureData, - types::PaymentsResponseData, + PaymentsCaptureData, + PaymentsResponseData, >, ) -> Result { let connector_meta = serde_json::json!(KlarnaMeta { @@ -664,8 +654,8 @@ impl let resource_id = item.data.request.connector_transaction_id.clone(); Ok(Self { - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(resource_id), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(resource_id), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: Some(connector_meta), @@ -704,12 +694,12 @@ pub struct KlarnaRefundResponse { pub refund_id: String, } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { // https://docs.klarna.com/api/ordermanagement/#operation/refundOrder // If 201 status code, then Refund is Successful, other status codes are handled by the error handler @@ -719,7 +709,7 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { // https://docs.klarna.com/api/ordermanagement/#operation/get // If 200 status code, then Refund is Successful, other status codes are handled by the error handler @@ -748,7 +738,7 @@ impl TryFrom for Mifinity +impl ConnectorIntegration + for Mifinity { // Not Implemented (R) } @@ -70,9 +88,9 @@ where { fn build_headers( &self, - req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![ ( headers::CONTENT_TYPE.to_string(), @@ -102,14 +120,14 @@ impl ConnectorCommon for Mifinity { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.mifinity.base_url.as_ref() } fn get_auth_header( &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { let auth = mifinity::MifinityAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( @@ -126,9 +144,9 @@ impl ConnectorCommon for Mifinity { if res.response.is_empty() { Ok(ErrorResponse { status_code: res.status_code, - code: consts::NO_ERROR_CODE.to_string(), - message: consts::NO_ERROR_MESSAGE.to_string(), - reason: Some(consts::CONNECTOR_UNAUTHORIZED_ERROR.to_string()), + code: NO_ERROR_CODE.to_string(), + message: NO_ERROR_MESSAGE.to_string(), + reason: Some(CONNECTOR_UNAUTHORIZED_ERROR.to_string()), attempt_status: None, connector_transaction_id: None, }) @@ -183,34 +201,23 @@ impl ConnectorValidation for Mifinity { //TODO: implement functions when support enabled } -impl ConnectorIntegration - for Mifinity -{ +impl ConnectorIntegration for Mifinity { //TODO: implement sessions flow } -impl ConnectorIntegration - for Mifinity -{ -} +impl ConnectorIntegration for Mifinity {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Mifinity +impl ConnectorIntegration + for Mifinity { } -impl ConnectorIntegration - for Mifinity -{ +impl ConnectorIntegration for Mifinity { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -220,8 +227,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}pegasus-ci/api/gateway/init-iframe", @@ -231,8 +238,8 @@ impl ConnectorIntegration CustomResult { let amount = convert_amount( self.amount_converter, @@ -247,12 +254,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -269,17 +276,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: mifinity::MifinityPaymentsResponse = res .response .parse_struct("Mifinity PaymentsAuthorizeResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -302,14 +309,12 @@ impl ConnectorIntegration - for Mifinity -{ +impl ConnectorIntegration for Mifinity { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -319,8 +324,8 @@ impl ConnectorIntegration CustomResult { let merchant_id = &req.merchant_id; let payment_id = &req.connector_request_reference_id; @@ -334,12 +339,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Get) + RequestBuilder::new() + .method(Method::Get) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -349,10 +354,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: mifinity::MifinityPsyncResponse = res .response .parse_struct("mifinity PaymentsSyncResponse") @@ -360,7 +365,7 @@ impl ConnectorIntegration - for Mifinity -{ +impl ConnectorIntegration for Mifinity { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -393,28 +396,28 @@ impl ConnectorIntegration CustomResult { Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) } fn get_request_body( &self, - _req: &types::PaymentsCaptureRouterData, - _connectors: &settings::Connectors, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, ) -> CustomResult { Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) } fn build_request( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -429,17 +432,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: mifinity::MifinityPaymentsResponse = res .response .parse_struct("Mifinity PaymentsCaptureResponse") .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 { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -455,19 +458,14 @@ impl ConnectorIntegration - for Mifinity -{ -} +impl ConnectorIntegration for Mifinity {} -impl ConnectorIntegration - for Mifinity -{ +impl ConnectorIntegration for Mifinity { fn build_request( &self, - _req: &types::RefundsRouterData, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err(errors::ConnectorError::FlowNotSupported { flow: "Refunds".to_string(), connector: "Mifinity".to_string(), @@ -476,27 +474,27 @@ impl ConnectorIntegration for Mifinity {} +impl ConnectorIntegration for Mifinity {} #[async_trait::async_trait] -impl api::IncomingWebhook for Mifinity { +impl IncomingWebhook for Mifinity { fn get_webhook_object_reference_id( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_event_type( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } fn get_webhook_resource_object( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Err(report!(errors::ConnectorError::WebhooksNotImplemented)) } diff --git a/crates/router/src/connector/mifinity/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs similarity index 72% rename from crates/router/src/connector/mifinity/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs index f913077d1dc..f89542d7810 100644 --- a/crates/router/src/connector/mifinity/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs @@ -1,17 +1,24 @@ +use common_enums::enums; use common_utils::{ pii::{self, Email}, types::StringMajorUnit, }; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::{PaymentMethodData, WalletData}, + router_data::{ConnectorAuthType, RouterData}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RedirectForm}, + types, +}; +use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; use time::Date; use crate::{ - connector::utils::{self, PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData}, - core::errors, - services, - types::{self, domain, storage::enums}, + types::ResponseRouterData, + utils::{self, PaymentsAuthorizeRequestData, PhoneDetailsData, RouterData as _}, }; pub struct MifinityRouterData { @@ -102,8 +109,8 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin config: "merchant_connector_account.metadata", })?; match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - domain::WalletData::Mifinity(data) => { + PaymentMethodData::Wallet(wallet_data) => match wallet_data { + WalletData::Mifinity(data) => { let money = Money { amount: item.amount.clone(), currency: item.router_data.request.currency.to_string(), @@ -151,56 +158,56 @@ impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for Mifin language_preference, }) } - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::AmazonPayRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePay(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePay(_) - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::Paze(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePay(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePay(_) + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Mifinity"), ) .into()), }, - domain::PaymentMethodData::Card(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::BankRedirect(_) - | domain::PaymentMethodData::PayLater(_) - | domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::MandatePayment - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::Card(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Mifinity"), ) @@ -215,11 +222,11 @@ pub struct MifinityAuthType { pub(super) key: Secret, } -impl TryFrom<&types::ConnectorAuthType> for MifinityAuthType { +impl TryFrom<&ConnectorAuthType> for MifinityAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { key: api_key.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), @@ -239,18 +246,12 @@ pub struct MifinityPayload { initialization_token: String, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData< - F, - MifinityPaymentsResponse, - T, - types::PaymentsResponseData, - >, + item: ResponseRouterData, ) -> Result { let payload = item.response.payload.first(); match payload { @@ -259,9 +260,9 @@ impl let initialization_token = payload.initialization_token.clone(); Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(trace_id.clone()), - redirection_data: Box::new(Some(services::RedirectForm::Mifinity { + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(trace_id.clone()), + redirection_data: Box::new(Some(RedirectForm::Mifinity { initialization_token, })), mandate_reference: Box::new(None), @@ -276,8 +277,8 @@ impl } None => Ok(Self { status: enums::AttemptStatus::AuthenticationPending, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -323,13 +324,12 @@ pub enum MifinityPaymentStatus { NotCompleted, } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let payload = item.response.payload.first(); @@ -343,8 +343,8 @@ impl let transaction_reference = payment_response.transaction_reference.clone(); Ok(Self { status: enums::AttemptStatus::from(status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( transaction_reference, ), redirection_data: Box::new(None), @@ -360,8 +360,8 @@ impl } None => Ok(Self { status: enums::AttemptStatus::from(status), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -376,8 +376,8 @@ impl } None => Ok(Self { status: item.data.status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::NoResponseId, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, diff --git a/crates/router/src/connector/nuvei.rs b/crates/hyperswitch_connectors/src/connectors/nuvei.rs similarity index 67% rename from crates/router/src/connector/nuvei.rs rename to crates/hyperswitch_connectors/src/connectors/nuvei.rs index c72bee24fa2..803a92b7c12 100644 --- a/crates/router/src/connector/nuvei.rs +++ b/crates/hyperswitch_connectors/src/connectors/nuvei.rs @@ -2,33 +2,56 @@ pub mod transformers; use std::fmt::Debug; -use ::common_utils::{ +use api_models::{payments::PaymentIdType, webhooks::IncomingWebhookEvent}; +use common_enums::{enums, CallConnectorAction, PaymentAction}; +use common_utils::{ crypto, - errors::ReportSwitchExt, - ext_traits::{BytesExt, ValueExt}, - request::RequestContent, + errors::{CustomResult, ReportSwitchExt}, + ext_traits::{ByteSliceExt, BytesExt, ValueExt}, + id_type, + request::{Method, Request, RequestBuilder, RequestContent}, }; use error_stack::ResultExt; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + AuthorizeSessionToken, CompleteAuthorize, PreProcessing, + }, + router_request_types::{ + AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, + PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, + PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsAuthorizeSessionTokenRouterData, + PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorRedirectResponse, + ConnectorSpecifications, ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks::{IncomingWebhook, IncomingWebhookRequestDetails}, +}; use masking::ExposeInterface; use transformers as nuvei; -use super::utils::{self, is_mandate_supported, PaymentMethodDataType, RouterData}; use crate::{ - configs::settings, - core::{ - errors::{self, CustomResult}, - payments, - }, - events::connector_api_logs::ConnectorEvent, - headers, - services::{self, request, ConnectorIntegration, ConnectorSpecifications, ConnectorValidation}, - types::{ - self, - api::{self, ConnectorCommon, ConnectorCommonExt}, - storage::enums, - ErrorResponse, Response, - }, - utils::ByteSliceExt, + constants::headers, + types::ResponseRouterData, + utils::{self, is_mandate_supported, PaymentMethodDataType, RouterData as _}, }; #[derive(Debug, Clone)] @@ -40,9 +63,9 @@ where { fn build_headers( &self, - _req: &types::RouterData, - _connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { let headers = vec![( headers::CONTENT_TYPE.to_string(), self.get_content_type().to_string().into(), @@ -60,14 +83,14 @@ impl ConnectorCommon for Nuvei { "application/json" } - fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { connectors.nuvei.base_url.as_ref() } fn get_auth_header( &self, - _auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { + _auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { Ok(vec![]) } } @@ -93,7 +116,7 @@ impl ConnectorValidation for Nuvei { fn validate_mandate_payment( &self, pm_type: Option, - pm_data: types::domain::payments::PaymentMethodData, + pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { let mandate_supported_pmd = std::collections::HashSet::from([PaymentMethodDataType::Card]); is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) @@ -104,12 +127,8 @@ impl api::Payment for Nuvei {} impl api::PaymentToken for Nuvei {} -impl - ConnectorIntegration< - api::PaymentMethodToken, - types::PaymentMethodTokenizationData, - types::PaymentsResponseData, - > for Nuvei +impl ConnectorIntegration + for Nuvei { // Not Implemented (R) } @@ -128,22 +147,12 @@ impl api::PaymentsCompleteAuthorize for Nuvei {} impl api::ConnectorAccessToken for Nuvei {} impl api::PaymentsPreProcessing for Nuvei {} -impl - ConnectorIntegration< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - > for Nuvei -{ +impl ConnectorIntegration for Nuvei { fn build_request( &self, - _req: &types::RouterData< - api::SetupMandate, - types::SetupMandateRequestData, - types::PaymentsResponseData, - >, - _connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Err( errors::ConnectorError::NotImplemented("Setup Mandate flow for Nuvei".to_string()) .into(), @@ -151,18 +160,14 @@ impl } } -impl - ConnectorIntegration< - api::CompleteAuthorize, - types::CompleteAuthorizeData, - types::PaymentsResponseData, - > for Nuvei +impl ConnectorIntegration + for Nuvei { fn get_headers( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } fn get_content_type(&self) -> &'static str { @@ -170,8 +175,8 @@ impl } fn get_url( &self, - _req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, + _req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}ppp/api/v1/payment.do", @@ -180,8 +185,8 @@ impl } fn get_request_body( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - _connectors: &settings::Connectors, + req: &PaymentsCompleteAuthorizeRouterData, + _connectors: &Connectors, ) -> CustomResult { let meta: nuvei::NuveiMeta = utils::to_connector_meta(req.request.connector_meta.clone())?; let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, meta.session_token))?; @@ -190,12 +195,12 @@ impl } fn build_request( &self, - req: &types::PaymentsCompleteAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsCompleteAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCompleteAuthorizeType::get_url( self, req, connectors, )?) @@ -211,10 +216,10 @@ impl } fn handle_response( &self, - data: &types::PaymentsCompleteAuthorizeRouterData, + data: &PaymentsCompleteAuthorizeRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -223,7 +228,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -240,14 +245,12 @@ impl } } -impl ConnectorIntegration - for Nuvei -{ +impl ConnectorIntegration for Nuvei { fn get_headers( &self, - req: &types::PaymentsCancelRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -257,8 +260,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}ppp/api/v1/voidTransaction.do", @@ -268,8 +271,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -277,11 +280,11 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) @@ -294,17 +297,17 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") .switch()?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -321,19 +324,14 @@ impl ConnectorIntegration - for Nuvei -{ -} +impl ConnectorIntegration for Nuvei {} -impl ConnectorIntegration - for Nuvei -{ +impl ConnectorIntegration for Nuvei { fn get_headers( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -343,8 +341,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}ppp/api/v1/getPaymentStatus.do", @@ -354,20 +352,20 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentSyncRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( &self, - req: &types::PaymentsSyncRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) @@ -388,10 +386,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -400,7 +398,7 @@ impl ConnectorIntegration - for Nuvei -{ +impl ConnectorIntegration for Nuvei { fn get_headers( &self, - req: &types::PaymentsCaptureRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -426,8 +422,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}ppp/api/v1/settleTransaction.do", @@ -437,8 +433,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -446,12 +442,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::PaymentsCaptureType::get_headers( @@ -466,10 +462,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -478,7 +474,7 @@ impl ConnectorIntegration - for Nuvei -{ -} +impl ConnectorIntegration for Nuvei {} #[async_trait::async_trait] -impl ConnectorIntegration - for Nuvei -{ +impl ConnectorIntegration for Nuvei { fn get_headers( &self, - req: &types::PaymentsAuthorizeRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -518,8 +509,8 @@ impl ConnectorIntegration CustomResult { Ok(format!( "{}ppp/api/v1/payment.do", @@ -529,8 +520,8 @@ impl ConnectorIntegration CustomResult { let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; @@ -539,12 +530,12 @@ impl ConnectorIntegration CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsAuthorizeType::get_url( self, req, connectors, )?) @@ -561,10 +552,10 @@ impl ConnectorIntegration, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -573,7 +564,7 @@ impl ConnectorIntegration for Nuvei +impl ConnectorIntegration + for Nuvei { fn get_headers( &self, - req: &types::PaymentsAuthorizeSessionTokenRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsAuthorizeSessionTokenRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -611,8 +598,8 @@ impl fn get_url( &self, - _req: &types::PaymentsAuthorizeSessionTokenRouterData, - connectors: &settings::Connectors, + _req: &PaymentsAuthorizeSessionTokenRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}ppp/api/v1/getSessionToken.do", @@ -622,8 +609,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsAuthorizeSessionTokenRouterData, - _connectors: &settings::Connectors, + req: &PaymentsAuthorizeSessionTokenRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = nuvei::NuveiSessionRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -631,12 +618,12 @@ impl fn build_request( &self, - req: &types::PaymentsAuthorizeSessionTokenRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsAuthorizeSessionTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsPreAuthorizeType::get_url( self, req, connectors, )?) @@ -653,17 +640,17 @@ impl fn handle_response( &self, - data: &types::PaymentsAuthorizeSessionTokenRouterData, + data: &PaymentsAuthorizeSessionTokenRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiSessionResponse = res.response.parse_struct("NuveiSessionResponse").switch()?; event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -679,18 +666,14 @@ impl } } -impl - ConnectorIntegration< - api::PreProcessing, - types::PaymentsPreProcessingData, - types::PaymentsResponseData, - > for Nuvei +impl ConnectorIntegration + for Nuvei { fn get_headers( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -700,8 +683,8 @@ impl fn get_url( &self, - _req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}ppp/api/v1/initPayment.do", @@ -711,8 +694,8 @@ impl fn get_request_body( &self, - req: &types::PaymentsPreProcessingRouterData, - _connectors: &settings::Connectors, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = nuvei::NuveiPaymentsRequest::try_from((req, req.get_session_token()?))?; @@ -721,12 +704,12 @@ impl fn build_request( &self, - req: &types::PaymentsPreProcessingRouterData, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { Ok(Some( - services::RequestBuilder::new() - .method(services::Method::Post) + RequestBuilder::new() + .method(Method::Post) .url(&types::PaymentsPreProcessingType::get_url( self, req, connectors, )?) @@ -743,10 +726,10 @@ impl fn handle_response( &self, - data: &types::PaymentsPreProcessingRouterData, + data: &PaymentsPreProcessingRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult { + ) -> CustomResult { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -755,7 +738,7 @@ impl event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); - types::RouterData::try_from(types::ResponseRouterData { + RouterData::try_from(ResponseRouterData { response, data: data.clone(), http_code: res.status_code, @@ -772,12 +755,12 @@ impl } } -impl ConnectorIntegration for Nuvei { +impl ConnectorIntegration for Nuvei { fn get_headers( &self, - req: &types::RefundsRouterData, - connectors: &settings::Connectors, - ) -> CustomResult)>, errors::ConnectorError> { + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { self.build_headers(req, connectors) } @@ -787,8 +770,8 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, + _req: &RefundsRouterData, + connectors: &Connectors, ) -> CustomResult { Ok(format!( "{}ppp/api/v1/refundTransaction.do", @@ -798,8 +781,8 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + req: &RefundsRouterData, + _connectors: &Connectors, ) -> CustomResult { let connector_req = nuvei::NuveiPaymentFlowRequest::try_from(req)?; Ok(RequestContent::Json(Box::new(connector_req))) @@ -807,11 +790,11 @@ impl ConnectorIntegration, - connectors: &settings::Connectors, - ) -> CustomResult, errors::ConnectorError> { - let request = services::RequestBuilder::new() - .method(services::Method::Post) + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) .url(&types::RefundExecuteType::get_url(self, req, connectors)?) .attach_default_headers() .headers(types::RefundExecuteType::get_headers( @@ -826,10 +809,10 @@ impl ConnectorIntegration, + data: &RefundsRouterData, event_builder: Option<&mut ConnectorEvent>, res: Response, - ) -> CustomResult, errors::ConnectorError> { + ) -> CustomResult, errors::ConnectorError> { let response: nuvei::NuveiPaymentsResponse = res .response .parse_struct("NuveiPaymentsResponse") @@ -838,7 +821,7 @@ impl ConnectorIntegration for Nuvei {} +impl ConnectorIntegration for Nuvei {} #[async_trait::async_trait] -impl api::IncomingWebhook for Nuvei { +impl IncomingWebhook for Nuvei { fn get_webhook_source_verification_algorithm( &self, - _request: &api::IncomingWebhookRequestDetails<'_>, + _request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { Ok(Box::new(crypto::Sha256)) } fn get_webhook_source_verification_signature( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, _connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let signature = utils::get_header_key_value("advanceResponseChecksum", request.headers)?; @@ -877,8 +860,8 @@ impl api::IncomingWebhook for Nuvei { fn get_webhook_source_verification_message( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - _merchant_id: &common_utils::id_type::MerchantId, + request: &IncomingWebhookRequestDetails<'_>, + _merchant_id: &id_type::MerchantId, connector_webhook_secrets: &api_models::webhooks::ConnectorWebhookSecrets, ) -> CustomResult, errors::ConnectorError> { let body = serde_urlencoded::from_str::(&request.query_params) @@ -901,39 +884,35 @@ impl api::IncomingWebhook for Nuvei { fn get_webhook_object_reference_id( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult { let body = serde_urlencoded::from_str::(&request.query_params) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; Ok(api_models::webhooks::ObjectReferenceId::PaymentId( - api::PaymentIdType::ConnectorTransactionId(body.ppp_transaction_id), + PaymentIdType::ConnectorTransactionId(body.ppp_transaction_id), )) } fn get_webhook_event_type( &self, - request: &api::IncomingWebhookRequestDetails<'_>, - ) -> CustomResult { + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { let body = serde_urlencoded::from_str::(&request.query_params) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; match body.status { - nuvei::NuveiWebhookStatus::Approved => { - Ok(api::IncomingWebhookEvent::PaymentIntentSuccess) - } - nuvei::NuveiWebhookStatus::Declined => { - Ok(api::IncomingWebhookEvent::PaymentIntentFailure) - } + nuvei::NuveiWebhookStatus::Approved => Ok(IncomingWebhookEvent::PaymentIntentSuccess), + nuvei::NuveiWebhookStatus::Declined => Ok(IncomingWebhookEvent::PaymentIntentFailure), nuvei::NuveiWebhookStatus::Unknown | nuvei::NuveiWebhookStatus::Pending - | nuvei::NuveiWebhookStatus::Update => Ok(api::IncomingWebhookEvent::EventNotSupported), + | nuvei::NuveiWebhookStatus::Update => Ok(IncomingWebhookEvent::EventNotSupported), } } fn get_webhook_resource_object( &self, - request: &api::IncomingWebhookRequestDetails<'_>, + request: &IncomingWebhookRequestDetails<'_>, ) -> CustomResult, errors::ConnectorError> { let body = serde_urlencoded::from_str::(&request.query_params) .change_context(errors::ConnectorError::WebhookBodyDecodingFailed)?; @@ -943,19 +922,18 @@ impl api::IncomingWebhook for Nuvei { } } -impl services::ConnectorRedirectResponse for Nuvei { +impl ConnectorRedirectResponse for Nuvei { fn get_flow_type( &self, _query_params: &str, json_payload: Option, - action: services::PaymentAction, - ) -> CustomResult { + action: PaymentAction, + ) -> CustomResult { match action { - services::PaymentAction::PSync - | services::PaymentAction::PaymentAuthenticateCompleteAuthorize => { - Ok(payments::CallConnectorAction::Trigger) + PaymentAction::PSync | PaymentAction::PaymentAuthenticateCompleteAuthorize => { + Ok(CallConnectorAction::Trigger) } - services::PaymentAction::CompleteAuthorize => { + PaymentAction::CompleteAuthorize => { if let Some(payload) = json_payload { let redirect_response: nuvei::NuveiRedirectionResponse = payload.parse_value("NuveiRedirectionResponse").switch()?; @@ -966,16 +944,16 @@ impl services::ConnectorRedirectResponse for Nuvei { .switch()?; match acs_response.trans_status { None | Some(nuvei::LiabilityShift::Failed) => { - Ok(payments::CallConnectorAction::StatusUpdate { + Ok(CallConnectorAction::StatusUpdate { status: enums::AttemptStatus::AuthenticationFailed, error_code: None, error_message: None, }) } - _ => Ok(payments::CallConnectorAction::Trigger), + _ => Ok(CallConnectorAction::Trigger), } } else { - Ok(payments::CallConnectorAction::Trigger) + Ok(CallConnectorAction::Trigger) } } } diff --git a/crates/router/src/connector/nuvei/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs similarity index 72% rename from crates/router/src/connector/nuvei/transformers.rs rename to crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs index 136d0531d9a..84fb7bfecf2 100644 --- a/crates/router/src/connector/nuvei/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs @@ -1,28 +1,62 @@ +use common_enums::enums; use common_utils::{ crypto::{self, GenerateDigest}, date_time, - ext_traits::Encode, + ext_traits::{Encode, OptionExt}, fp_utils, pii::{Email, IpAddress}, + request::Method, }; use error_stack::ResultExt; -use hyperswitch_domain_models::mandates::{MandateData, MandateDataType}; +use hyperswitch_domain_models::{ + mandates::{MandateData, MandateDataType}, + payment_method_data::{ + self, ApplePayWalletData, BankRedirectData, GooglePayWalletData, PayLaterData, + PaymentMethodData, WalletData, + }, + router_data::{ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + refunds::{Execute, RSync}, + Authorize, Capture, CompleteAuthorize, PSync, Void, + }, + router_request_types::{ + BrowserInformation, PaymentsAuthorizeData, PaymentsPreProcessingData, ResponseId, + }, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, + types, +}; +use hyperswitch_interfaces::{ + consts::{NO_ERROR_CODE, NO_ERROR_MESSAGE}, + errors, +}; use masking::{ExposeInterface, PeekInterface, Secret}; -use reqwest::Url; use serde::{Deserialize, Serialize}; +use url::Url; use crate::{ - connector::utils::{ - self, AddressDetailsData, BrowserInformationData, PaymentsAuthorizeRequestData, - PaymentsCancelRequestData, PaymentsPreProcessingData, RouterData, + types::{ + PaymentsPreprocessingResponseRouterData, RefundsResponseRouterData, ResponseRouterData, + }, + utils::{ + self, AddressDetailsData, BrowserInformationData, ForeignTryFrom, + PaymentsAuthorizeRequestData, PaymentsCancelRequestData, PaymentsPreProcessingRequestData, + RouterData as _, }, - consts, - core::errors, - services, - types::{self, api, domain, storage::enums, transformers::ForeignTryFrom, BrowserInformation}, - utils::OptionExt, }; +fn to_boolean(string: String) -> bool { + let str = string.as_str(); + match str { + "true" => true, + "false" => false, + "yes" => true, + "no" => false, + _ => false, + } +} + trait NuveiAuthorizePreprocessingCommon { fn get_browser_info(&self) -> Option; fn get_related_transaction_id(&self) -> Option; @@ -37,13 +71,13 @@ trait NuveiAuthorizePreprocessingCommon { fn get_amount_required(&self) -> Result>; fn get_currency_required( &self, - ) -> Result>; + ) -> Result>; fn get_payment_method_data_required( &self, - ) -> Result>; + ) -> Result>; } -impl NuveiAuthorizePreprocessingCommon for types::PaymentsAuthorizeData { +impl NuveiAuthorizePreprocessingCommon for PaymentsAuthorizeData { fn get_browser_info(&self) -> Option { self.browser_info.clone() } @@ -84,17 +118,17 @@ impl NuveiAuthorizePreprocessingCommon for types::PaymentsAuthorizeData { fn get_currency_required( &self, - ) -> Result> { + ) -> Result> { Ok(self.currency) } fn get_payment_method_data_required( &self, - ) -> Result> { + ) -> Result> { Ok(self.payment_method_data.clone()) } } -impl NuveiAuthorizePreprocessingCommon for types::PaymentsPreProcessingData { +impl NuveiAuthorizePreprocessingCommon for PaymentsPreProcessingData { fn get_browser_info(&self) -> Option { self.browser_info.clone() } @@ -135,12 +169,12 @@ impl NuveiAuthorizePreprocessingCommon for types::PaymentsPreProcessingData { fn get_currency_required( &self, - ) -> Result> { + ) -> Result> { self.get_currency() } fn get_payment_method_data_required( &self, - ) -> Result> { + ) -> Result> { self.payment_method_data.clone().ok_or( errors::ConnectorError::MissingRequiredField { field_name: "payment_method_data", @@ -194,7 +228,7 @@ pub struct NuveiPaymentsRequest { pub merchant_site_id: Secret, pub client_request_id: Secret, pub amount: String, - pub currency: diesel_models::enums::Currency, + pub currency: enums::Currency, /// This ID uniquely identifies your consumer/user in your system. pub user_token_id: Option, pub client_unique_id: String, @@ -237,7 +271,7 @@ pub struct NuveiPaymentFlowRequest { pub merchant_site_id: Secret, pub client_request_id: String, pub amount: String, - pub currency: diesel_models::enums::Currency, + pub currency: enums::Currency, pub related_transaction_id: Option, pub checksum: Secret, } @@ -531,18 +565,17 @@ impl TryFrom<&types::PaymentsAuthorizeSessionTokenRouterData> for NuveiSessionRe } } -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { Ok(Self { status: enums::AttemptStatus::Pending, session_token: Some(item.response.session_token.clone().expose()), - response: Ok(types::PaymentsResponseData::SessionTokenResponse { + response: Ok(PaymentsResponseData::SessionTokenResponse { session_token: item.response.session_token.expose(), }), ..item.data @@ -552,14 +585,14 @@ impl #[derive(Debug)] pub struct NuveiCardDetails { - card: domain::Card, + card: payment_method_data::Card, three_d: Option, card_holder_name: Option>, } -impl TryFrom for NuveiPaymentsRequest { +impl TryFrom for NuveiPaymentsRequest { type Error = error_stack::Report; - fn try_from(gpay_data: domain::GooglePayWalletData) -> Result { + fn try_from(gpay_data: GooglePayWalletData) -> Result { Ok(Self { payment_option: PaymentOption { card: Some(Card { @@ -579,8 +612,8 @@ impl TryFrom for NuveiPaymentsRequest { }) } } -impl From for NuveiPaymentsRequest { - fn from(apple_pay_data: domain::ApplePayWalletData) -> Self { +impl From for NuveiPaymentsRequest { + fn from(apple_pay_data: ApplePayWalletData) -> Self { Self { payment_option: PaymentOption { card: Some(Card { @@ -597,161 +630,159 @@ impl From for NuveiPaymentsRequest { } } -impl TryFrom for NuveiBIC { +impl TryFrom for NuveiBIC { type Error = error_stack::Report; - fn try_from(bank: common_enums::enums::BankNames) -> Result { + fn try_from(bank: enums::BankNames) -> Result { match bank { - common_enums::enums::BankNames::AbnAmro => Ok(Self::Abnamro), - common_enums::enums::BankNames::AsnBank => Ok(Self::ASNBank), - common_enums::enums::BankNames::Bunq => Ok(Self::Bunq), - common_enums::enums::BankNames::Ing => Ok(Self::Ing), - common_enums::enums::BankNames::Knab => Ok(Self::Knab), - common_enums::enums::BankNames::Rabobank => Ok(Self::Rabobank), - common_enums::enums::BankNames::SnsBank => Ok(Self::SNSBank), - common_enums::enums::BankNames::TriodosBank => Ok(Self::TriodosBank), - common_enums::enums::BankNames::VanLanschot => Ok(Self::VanLanschotBankiers), - common_enums::enums::BankNames::Moneyou => Ok(Self::Moneyou), - - common_enums::enums::BankNames::AmericanExpress - | common_enums::enums::BankNames::AffinBank - | common_enums::enums::BankNames::AgroBank - | common_enums::enums::BankNames::AllianceBank - | common_enums::enums::BankNames::AmBank - | common_enums::enums::BankNames::BankOfAmerica - | common_enums::enums::BankNames::BankOfChina - | common_enums::enums::BankNames::BankIslam - | common_enums::enums::BankNames::BankMuamalat - | common_enums::enums::BankNames::BankRakyat - | common_enums::enums::BankNames::BankSimpananNasional - | common_enums::enums::BankNames::Barclays - | common_enums::enums::BankNames::BlikPSP - | common_enums::enums::BankNames::CapitalOne - | common_enums::enums::BankNames::Chase - | common_enums::enums::BankNames::Citi - | common_enums::enums::BankNames::CimbBank - | common_enums::enums::BankNames::Discover - | common_enums::enums::BankNames::NavyFederalCreditUnion - | common_enums::enums::BankNames::PentagonFederalCreditUnion - | common_enums::enums::BankNames::SynchronyBank - | common_enums::enums::BankNames::WellsFargo - | common_enums::enums::BankNames::Handelsbanken - | common_enums::enums::BankNames::HongLeongBank - | common_enums::enums::BankNames::HsbcBank - | common_enums::enums::BankNames::KuwaitFinanceHouse - | common_enums::enums::BankNames::Regiobank - | common_enums::enums::BankNames::Revolut - | common_enums::enums::BankNames::ArzteUndApothekerBank - | common_enums::enums::BankNames::AustrianAnadiBankAg - | common_enums::enums::BankNames::BankAustria - | common_enums::enums::BankNames::Bank99Ag - | common_enums::enums::BankNames::BankhausCarlSpangler - | common_enums::enums::BankNames::BankhausSchelhammerUndSchatteraAg - | common_enums::enums::BankNames::BankMillennium - | common_enums::enums::BankNames::BankPEKAOSA - | common_enums::enums::BankNames::BawagPskAg - | common_enums::enums::BankNames::BksBankAg - | common_enums::enums::BankNames::BrullKallmusBankAg - | common_enums::enums::BankNames::BtvVierLanderBank - | common_enums::enums::BankNames::CapitalBankGraweGruppeAg - | common_enums::enums::BankNames::CeskaSporitelna - | common_enums::enums::BankNames::Dolomitenbank - | common_enums::enums::BankNames::EasybankAg - | common_enums::enums::BankNames::EPlatbyVUB - | common_enums::enums::BankNames::ErsteBankUndSparkassen - | common_enums::enums::BankNames::FrieslandBank - | common_enums::enums::BankNames::HypoAlpeadriabankInternationalAg - | common_enums::enums::BankNames::HypoNoeLbFurNiederosterreichUWien - | common_enums::enums::BankNames::HypoOberosterreichSalzburgSteiermark - | common_enums::enums::BankNames::HypoTirolBankAg - | common_enums::enums::BankNames::HypoVorarlbergBankAg - | common_enums::enums::BankNames::HypoBankBurgenlandAktiengesellschaft - | common_enums::enums::BankNames::KomercniBanka - | common_enums::enums::BankNames::MBank - | common_enums::enums::BankNames::MarchfelderBank - | common_enums::enums::BankNames::Maybank - | common_enums::enums::BankNames::OberbankAg - | common_enums::enums::BankNames::OsterreichischeArzteUndApothekerbank - | common_enums::enums::BankNames::OcbcBank - | common_enums::enums::BankNames::PayWithING - | common_enums::enums::BankNames::PlaceZIPKO - | common_enums::enums::BankNames::PlatnoscOnlineKartaPlatnicza - | common_enums::enums::BankNames::PosojilnicaBankEGen - | common_enums::enums::BankNames::PostovaBanka - | common_enums::enums::BankNames::PublicBank - | common_enums::enums::BankNames::RaiffeisenBankengruppeOsterreich - | common_enums::enums::BankNames::RhbBank - | common_enums::enums::BankNames::SchelhammerCapitalBankAg - | common_enums::enums::BankNames::StandardCharteredBank - | common_enums::enums::BankNames::SchoellerbankAg - | common_enums::enums::BankNames::SpardaBankWien - | common_enums::enums::BankNames::SporoPay - | common_enums::enums::BankNames::SantanderPrzelew24 - | common_enums::enums::BankNames::TatraPay - | common_enums::enums::BankNames::Viamo - | common_enums::enums::BankNames::VolksbankGruppe - | common_enums::enums::BankNames::VolkskreditbankAg - | common_enums::enums::BankNames::VrBankBraunau - | common_enums::enums::BankNames::UobBank - | common_enums::enums::BankNames::PayWithAliorBank - | common_enums::enums::BankNames::BankiSpoldzielcze - | common_enums::enums::BankNames::PayWithInteligo - | common_enums::enums::BankNames::BNPParibasPoland - | common_enums::enums::BankNames::BankNowySA - | common_enums::enums::BankNames::CreditAgricole - | common_enums::enums::BankNames::PayWithBOS - | common_enums::enums::BankNames::PayWithCitiHandlowy - | common_enums::enums::BankNames::PayWithPlusBank - | common_enums::enums::BankNames::ToyotaBank - | common_enums::enums::BankNames::VeloBank - | common_enums::enums::BankNames::ETransferPocztowy24 - | common_enums::enums::BankNames::PlusBank - | common_enums::enums::BankNames::EtransferPocztowy24 - | common_enums::enums::BankNames::BankiSpbdzielcze - | common_enums::enums::BankNames::BankNowyBfgSa - | common_enums::enums::BankNames::GetinBank - | common_enums::enums::BankNames::Blik - | common_enums::enums::BankNames::NoblePay - | common_enums::enums::BankNames::IdeaBank - | common_enums::enums::BankNames::EnveloBank - | common_enums::enums::BankNames::NestPrzelew - | common_enums::enums::BankNames::MbankMtransfer - | common_enums::enums::BankNames::Inteligo - | common_enums::enums::BankNames::PbacZIpko - | common_enums::enums::BankNames::BnpParibas - | common_enums::enums::BankNames::BankPekaoSa - | common_enums::enums::BankNames::VolkswagenBank - | common_enums::enums::BankNames::AliorBank - | common_enums::enums::BankNames::Boz - | common_enums::enums::BankNames::BangkokBank - | common_enums::enums::BankNames::KrungsriBank - | common_enums::enums::BankNames::KrungThaiBank - | common_enums::enums::BankNames::TheSiamCommercialBank - | common_enums::enums::BankNames::KasikornBank - | common_enums::enums::BankNames::OpenBankSuccess - | common_enums::enums::BankNames::OpenBankFailure - | common_enums::enums::BankNames::OpenBankCancelled - | common_enums::enums::BankNames::Aib - | common_enums::enums::BankNames::BankOfScotland - | common_enums::enums::BankNames::DanskeBank - | common_enums::enums::BankNames::FirstDirect - | common_enums::enums::BankNames::FirstTrust - | common_enums::enums::BankNames::Halifax - | common_enums::enums::BankNames::Lloyds - | common_enums::enums::BankNames::Monzo - | common_enums::enums::BankNames::NatWest - | common_enums::enums::BankNames::NationwideBank - | common_enums::enums::BankNames::RoyalBankOfScotland - | common_enums::enums::BankNames::Starling - | common_enums::enums::BankNames::TsbBank - | common_enums::enums::BankNames::TescoBank - | common_enums::enums::BankNames::Yoursafe - | common_enums::enums::BankNames::N26 - | common_enums::enums::BankNames::NationaleNederlanden - | common_enums::enums::BankNames::UlsterBank => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("Nuvei"), - ))? - } + enums::BankNames::AbnAmro => Ok(Self::Abnamro), + enums::BankNames::AsnBank => Ok(Self::ASNBank), + enums::BankNames::Bunq => Ok(Self::Bunq), + enums::BankNames::Ing => Ok(Self::Ing), + enums::BankNames::Knab => Ok(Self::Knab), + enums::BankNames::Rabobank => Ok(Self::Rabobank), + enums::BankNames::SnsBank => Ok(Self::SNSBank), + enums::BankNames::TriodosBank => Ok(Self::TriodosBank), + enums::BankNames::VanLanschot => Ok(Self::VanLanschotBankiers), + enums::BankNames::Moneyou => Ok(Self::Moneyou), + + enums::BankNames::AmericanExpress + | enums::BankNames::AffinBank + | enums::BankNames::AgroBank + | enums::BankNames::AllianceBank + | enums::BankNames::AmBank + | enums::BankNames::BankOfAmerica + | enums::BankNames::BankOfChina + | enums::BankNames::BankIslam + | enums::BankNames::BankMuamalat + | enums::BankNames::BankRakyat + | enums::BankNames::BankSimpananNasional + | enums::BankNames::Barclays + | enums::BankNames::BlikPSP + | enums::BankNames::CapitalOne + | enums::BankNames::Chase + | enums::BankNames::Citi + | enums::BankNames::CimbBank + | enums::BankNames::Discover + | enums::BankNames::NavyFederalCreditUnion + | enums::BankNames::PentagonFederalCreditUnion + | enums::BankNames::SynchronyBank + | enums::BankNames::WellsFargo + | enums::BankNames::Handelsbanken + | enums::BankNames::HongLeongBank + | enums::BankNames::HsbcBank + | enums::BankNames::KuwaitFinanceHouse + | enums::BankNames::Regiobank + | enums::BankNames::Revolut + | enums::BankNames::ArzteUndApothekerBank + | enums::BankNames::AustrianAnadiBankAg + | enums::BankNames::BankAustria + | enums::BankNames::Bank99Ag + | enums::BankNames::BankhausCarlSpangler + | enums::BankNames::BankhausSchelhammerUndSchatteraAg + | enums::BankNames::BankMillennium + | enums::BankNames::BankPEKAOSA + | enums::BankNames::BawagPskAg + | enums::BankNames::BksBankAg + | enums::BankNames::BrullKallmusBankAg + | enums::BankNames::BtvVierLanderBank + | enums::BankNames::CapitalBankGraweGruppeAg + | enums::BankNames::CeskaSporitelna + | enums::BankNames::Dolomitenbank + | enums::BankNames::EasybankAg + | enums::BankNames::EPlatbyVUB + | enums::BankNames::ErsteBankUndSparkassen + | enums::BankNames::FrieslandBank + | enums::BankNames::HypoAlpeadriabankInternationalAg + | enums::BankNames::HypoNoeLbFurNiederosterreichUWien + | enums::BankNames::HypoOberosterreichSalzburgSteiermark + | enums::BankNames::HypoTirolBankAg + | enums::BankNames::HypoVorarlbergBankAg + | enums::BankNames::HypoBankBurgenlandAktiengesellschaft + | enums::BankNames::KomercniBanka + | enums::BankNames::MBank + | enums::BankNames::MarchfelderBank + | enums::BankNames::Maybank + | enums::BankNames::OberbankAg + | enums::BankNames::OsterreichischeArzteUndApothekerbank + | enums::BankNames::OcbcBank + | enums::BankNames::PayWithING + | enums::BankNames::PlaceZIPKO + | enums::BankNames::PlatnoscOnlineKartaPlatnicza + | enums::BankNames::PosojilnicaBankEGen + | enums::BankNames::PostovaBanka + | enums::BankNames::PublicBank + | enums::BankNames::RaiffeisenBankengruppeOsterreich + | enums::BankNames::RhbBank + | enums::BankNames::SchelhammerCapitalBankAg + | enums::BankNames::StandardCharteredBank + | enums::BankNames::SchoellerbankAg + | enums::BankNames::SpardaBankWien + | enums::BankNames::SporoPay + | enums::BankNames::SantanderPrzelew24 + | enums::BankNames::TatraPay + | enums::BankNames::Viamo + | enums::BankNames::VolksbankGruppe + | enums::BankNames::VolkskreditbankAg + | enums::BankNames::VrBankBraunau + | enums::BankNames::UobBank + | enums::BankNames::PayWithAliorBank + | enums::BankNames::BankiSpoldzielcze + | enums::BankNames::PayWithInteligo + | enums::BankNames::BNPParibasPoland + | enums::BankNames::BankNowySA + | enums::BankNames::CreditAgricole + | enums::BankNames::PayWithBOS + | enums::BankNames::PayWithCitiHandlowy + | enums::BankNames::PayWithPlusBank + | enums::BankNames::ToyotaBank + | enums::BankNames::VeloBank + | enums::BankNames::ETransferPocztowy24 + | enums::BankNames::PlusBank + | enums::BankNames::EtransferPocztowy24 + | enums::BankNames::BankiSpbdzielcze + | enums::BankNames::BankNowyBfgSa + | enums::BankNames::GetinBank + | enums::BankNames::Blik + | enums::BankNames::NoblePay + | enums::BankNames::IdeaBank + | enums::BankNames::EnveloBank + | enums::BankNames::NestPrzelew + | enums::BankNames::MbankMtransfer + | enums::BankNames::Inteligo + | enums::BankNames::PbacZIpko + | enums::BankNames::BnpParibas + | enums::BankNames::BankPekaoSa + | enums::BankNames::VolkswagenBank + | enums::BankNames::AliorBank + | enums::BankNames::Boz + | enums::BankNames::BangkokBank + | enums::BankNames::KrungsriBank + | enums::BankNames::KrungThaiBank + | enums::BankNames::TheSiamCommercialBank + | enums::BankNames::KasikornBank + | enums::BankNames::OpenBankSuccess + | enums::BankNames::OpenBankFailure + | enums::BankNames::OpenBankCancelled + | enums::BankNames::Aib + | enums::BankNames::BankOfScotland + | enums::BankNames::DanskeBank + | enums::BankNames::FirstDirect + | enums::BankNames::FirstTrust + | enums::BankNames::Halifax + | enums::BankNames::Lloyds + | enums::BankNames::Monzo + | enums::BankNames::NatWest + | enums::BankNames::NationwideBank + | enums::BankNames::RoyalBankOfScotland + | enums::BankNames::Starling + | enums::BankNames::TsbBank + | enums::BankNames::TescoBank + | enums::BankNames::Yoursafe + | enums::BankNames::N26 + | enums::BankNames::NationaleNederlanden + | enums::BankNames::UlsterBank => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Nuvei"), + ))?, } } } @@ -759,8 +790,8 @@ impl TryFrom for NuveiBIC { impl ForeignTryFrom<( AlternativePaymentMethodType, - Option, - &types::RouterData, + Option, + &RouterData, )> for NuveiPaymentsRequest where Req: NuveiAuthorizePreprocessingCommon, @@ -769,8 +800,8 @@ where fn foreign_try_from( data: ( AlternativePaymentMethodType, - Option, - &types::RouterData, + Option, + &RouterData, ), ) -> Result { let (payment_method, redirect, item) = data; @@ -806,7 +837,7 @@ where } ( AlternativePaymentMethodType::Ideal, - Some(domain::BankRedirectData::Ideal { bank_name, .. }), + Some(BankRedirectData::Ideal { bank_name, .. }), ) => { let address = item.get_billing_address()?; let first_name = address.get_first_name()?.clone(); @@ -842,7 +873,7 @@ where fn get_pay_later_info( payment_method_type: AlternativePaymentMethodType, - item: &types::RouterData, + item: &RouterData, ) -> Result> where Req: NuveiAuthorizePreprocessingCommon, @@ -872,137 +903,128 @@ where }) } -impl - TryFrom<( - &types::RouterData, - String, - )> for NuveiPaymentsRequest +impl TryFrom<(&RouterData, String)> for NuveiPaymentsRequest where Req: NuveiAuthorizePreprocessingCommon, { type Error = error_stack::Report; fn try_from( - data: ( - &types::RouterData, - String, - ), + data: (&RouterData, String), ) -> Result { let item = data.0; let request_data = match item.request.get_payment_method_data_required()?.clone() { - domain::PaymentMethodData::Card(card) => get_card_info(item, &card), - domain::PaymentMethodData::MandatePayment => Self::try_from(item), - domain::PaymentMethodData::Wallet(wallet) => match wallet { - domain::WalletData::GooglePay(gpay_data) => Self::try_from(gpay_data), - domain::WalletData::ApplePay(apple_pay_data) => Ok(Self::from(apple_pay_data)), - domain::WalletData::PaypalRedirect(_) => Self::foreign_try_from(( + PaymentMethodData::Card(card) => get_card_info(item, &card), + PaymentMethodData::MandatePayment => Self::try_from(item), + PaymentMethodData::Wallet(wallet) => match wallet { + WalletData::GooglePay(gpay_data) => Self::try_from(gpay_data), + WalletData::ApplePay(apple_pay_data) => Ok(Self::from(apple_pay_data)), + WalletData::PaypalRedirect(_) => Self::foreign_try_from(( AlternativePaymentMethodType::Expresscheckout, None, item, )), - domain::WalletData::AliPayQr(_) - | domain::WalletData::AliPayRedirect(_) - | domain::WalletData::AliPayHkRedirect(_) - | domain::WalletData::AmazonPayRedirect(_) - | domain::WalletData::MomoRedirect(_) - | domain::WalletData::KakaoPayRedirect(_) - | domain::WalletData::GoPayRedirect(_) - | domain::WalletData::GcashRedirect(_) - | domain::WalletData::ApplePayRedirect(_) - | domain::WalletData::ApplePayThirdPartySdk(_) - | domain::WalletData::DanaRedirect {} - | domain::WalletData::GooglePayRedirect(_) - | domain::WalletData::GooglePayThirdPartySdk(_) - | domain::WalletData::MbWayRedirect(_) - | domain::WalletData::MobilePayRedirect(_) - | domain::WalletData::PaypalSdk(_) - | domain::WalletData::Paze(_) - | domain::WalletData::SamsungPay(_) - | domain::WalletData::TwintRedirect {} - | domain::WalletData::VippsRedirect {} - | domain::WalletData::TouchNGoRedirect(_) - | domain::WalletData::WeChatPayRedirect(_) - | domain::WalletData::CashappQr(_) - | domain::WalletData::SwishQr(_) - | domain::WalletData::WeChatPayQr(_) - | domain::WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::AmazonPayRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::WeChatPayQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), ) .into()), }, - domain::PaymentMethodData::BankRedirect(redirect) => match redirect { - domain::BankRedirectData::Eps { .. } => Self::foreign_try_from(( + PaymentMethodData::BankRedirect(redirect) => match redirect { + BankRedirectData::Eps { .. } => Self::foreign_try_from(( AlternativePaymentMethodType::Eps, Some(redirect), item, )), - domain::BankRedirectData::Giropay { .. } => Self::foreign_try_from(( + BankRedirectData::Giropay { .. } => Self::foreign_try_from(( AlternativePaymentMethodType::Giropay, Some(redirect), item, )), - domain::BankRedirectData::Ideal { .. } => Self::foreign_try_from(( + BankRedirectData::Ideal { .. } => Self::foreign_try_from(( AlternativePaymentMethodType::Ideal, Some(redirect), item, )), - domain::BankRedirectData::Sofort { .. } => Self::foreign_try_from(( + BankRedirectData::Sofort { .. } => Self::foreign_try_from(( AlternativePaymentMethodType::Sofort, Some(redirect), item, )), - domain::BankRedirectData::BancontactCard { .. } - | domain::BankRedirectData::Bizum {} - | domain::BankRedirectData::Blik { .. } - | domain::BankRedirectData::Interac { .. } - | domain::BankRedirectData::OnlineBankingCzechRepublic { .. } - | domain::BankRedirectData::OnlineBankingFinland { .. } - | domain::BankRedirectData::OnlineBankingPoland { .. } - | domain::BankRedirectData::OnlineBankingSlovakia { .. } - | domain::BankRedirectData::Przelewy24 { .. } - | domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OnlineBankingFpx { .. } - | domain::BankRedirectData::OnlineBankingThailand { .. } - | domain::BankRedirectData::OpenBankingUk { .. } - | domain::BankRedirectData::LocalBankRedirect {} => { + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Blik { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingFpx { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), ) .into()) } }, - domain::PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { - domain::PayLaterData::KlarnaRedirect { .. } => { + PaymentMethodData::PayLater(pay_later_data) => match pay_later_data { + PayLaterData::KlarnaRedirect { .. } => { get_pay_later_info(AlternativePaymentMethodType::Klarna, item) } - domain::PayLaterData::AfterpayClearpayRedirect { .. } => { + PayLaterData::AfterpayClearpayRedirect { .. } => { get_pay_later_info(AlternativePaymentMethodType::AfterPay, item) } - domain::PayLaterData::KlarnaSdk { .. } - | domain::PayLaterData::AffirmRedirect {} - | domain::PayLaterData::PayBrightRedirect {} - | domain::PayLaterData::WalleyRedirect {} - | domain::PayLaterData::AlmaRedirect {} - | domain::PayLaterData::AtomeRedirect {} => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("nuvei"), - ) - .into()) - } + PayLaterData::KlarnaSdk { .. } + | PayLaterData::AffirmRedirect {} + | PayLaterData::PayBrightRedirect {} + | PayLaterData::WalleyRedirect {} + | PayLaterData::AlmaRedirect {} + | PayLaterData::AtomeRedirect {} => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("nuvei"), + ) + .into()), }, - domain::PaymentMethodData::BankDebit(_) - | domain::PaymentMethodData::BankTransfer(_) - | domain::PaymentMethodData::Crypto(_) - | domain::PaymentMethodData::Reward - | domain::PaymentMethodData::RealTimePayment(_) - | domain::PaymentMethodData::MobilePayment(_) - | domain::PaymentMethodData::Upi(_) - | domain::PaymentMethodData::Voucher(_) - | domain::PaymentMethodData::CardRedirect(_) - | domain::PaymentMethodData::GiftCard(_) - | domain::PaymentMethodData::OpenBanking(_) - | domain::PaymentMethodData::CardToken(_) - | domain::PaymentMethodData::NetworkToken(_) - | domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), ) @@ -1038,8 +1060,8 @@ where } fn get_card_info( - item: &types::RouterData, - card_details: &domain::Card, + item: &RouterData, + card_details: &payment_method_data::Card, ) -> Result> where Req: NuveiAuthorizePreprocessingCommon, @@ -1182,7 +1204,7 @@ impl TryFrom<(&types::PaymentsCompleteAuthorizeRouterData, Secret)> ) -> Result { let item = data.0; let request_data = match item.request.payment_method_data.clone() { - Some(domain::PaymentMethodData::Card(card)) => Ok(Self { + Some(PaymentMethodData::Card(card)) => Ok(Self { payment_option: PaymentOption::from(NuveiCardDetails { card, three_d: None, @@ -1190,24 +1212,24 @@ impl TryFrom<(&types::PaymentsCompleteAuthorizeRouterData, Secret)> }), ..Default::default() }), - Some(domain::PaymentMethodData::Wallet(..)) - | Some(domain::PaymentMethodData::PayLater(..)) - | Some(domain::PaymentMethodData::BankDebit(..)) - | Some(domain::PaymentMethodData::BankRedirect(..)) - | Some(domain::PaymentMethodData::BankTransfer(..)) - | Some(domain::PaymentMethodData::Crypto(..)) - | Some(domain::PaymentMethodData::MandatePayment) - | Some(domain::PaymentMethodData::GiftCard(..)) - | Some(domain::PaymentMethodData::Voucher(..)) - | Some(domain::PaymentMethodData::CardRedirect(..)) - | Some(domain::PaymentMethodData::Reward) - | Some(domain::PaymentMethodData::RealTimePayment(..)) - | Some(domain::PaymentMethodData::MobilePayment(..)) - | Some(domain::PaymentMethodData::Upi(..)) - | Some(domain::PaymentMethodData::OpenBanking(_)) - | Some(domain::PaymentMethodData::CardToken(..)) - | Some(domain::PaymentMethodData::NetworkToken(..)) - | Some(domain::PaymentMethodData::CardDetailsForNetworkTransactionId(_)) + Some(PaymentMethodData::Wallet(..)) + | Some(PaymentMethodData::PayLater(..)) + | Some(PaymentMethodData::BankDebit(..)) + | Some(PaymentMethodData::BankRedirect(..)) + | Some(PaymentMethodData::BankTransfer(..)) + | Some(PaymentMethodData::Crypto(..)) + | Some(PaymentMethodData::MandatePayment) + | Some(PaymentMethodData::GiftCard(..)) + | Some(PaymentMethodData::Voucher(..)) + | Some(PaymentMethodData::CardRedirect(..)) + | Some(PaymentMethodData::Reward) + | Some(PaymentMethodData::RealTimePayment(..)) + | Some(PaymentMethodData::MobilePayment(..)) + | Some(PaymentMethodData::Upi(..)) + | Some(PaymentMethodData::OpenBanking(_)) + | Some(PaymentMethodData::CardToken(..)) + | Some(PaymentMethodData::NetworkToken(..)) + | Some(PaymentMethodData::CardDetailsForNetworkTransactionId(_)) | None => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("nuvei"), )), @@ -1306,12 +1328,12 @@ impl TryFrom for NuveiPaymentFlowRequest { #[derive(Debug, Clone, Default)] pub struct NuveiPaymentRequestData { pub amount: String, - pub currency: diesel_models::enums::Currency, + pub currency: enums::Currency, pub related_transaction_id: Option, pub client_request_id: String, - pub connector_auth_type: types::ConnectorAuthType, + pub connector_auth_type: ConnectorAuthType, pub session_token: Secret, - pub capture_method: Option, + pub capture_method: Option, } impl TryFrom<&types::PaymentsCaptureRouterData> for NuveiPaymentFlowRequest { @@ -1381,10 +1403,10 @@ pub struct NuveiAuthType { pub(super) merchant_secret: Secret, } -impl TryFrom<&types::ConnectorAuthType> for NuveiAuthType { +impl TryFrom<&ConnectorAuthType> for NuveiAuthType { type Error = error_stack::Report; - fn try_from(auth_type: &types::ConnectorAuthType) -> Result { - if let types::ConnectorAuthType::SignatureKey { + fn try_from(auth_type: &ConnectorAuthType) -> Result { + if let ConnectorAuthType::SignatureKey { api_key, key1, api_secret, @@ -1518,7 +1540,7 @@ fn get_payment_status(response: &NuveiPaymentsResponse) -> enums::AttemptStatus fn build_error_response( response: &NuveiPaymentsResponse, http_code: u16, -) -> Option> { +) -> Option> { match response.status { NuveiPaymentStatus::Error => Some(get_error_response( response.err_code, @@ -1548,30 +1570,28 @@ fn build_error_response( pub trait NuveiPaymentsGenericResponse {} -impl NuveiPaymentsGenericResponse for api::Authorize {} -impl NuveiPaymentsGenericResponse for api::CompleteAuthorize {} -impl NuveiPaymentsGenericResponse for api::Void {} -impl NuveiPaymentsGenericResponse for api::PSync {} -impl NuveiPaymentsGenericResponse for api::Capture {} +impl NuveiPaymentsGenericResponse for Authorize {} +impl NuveiPaymentsGenericResponse for CompleteAuthorize {} +impl NuveiPaymentsGenericResponse for Void {} +impl NuveiPaymentsGenericResponse for PSync {} +impl NuveiPaymentsGenericResponse for Capture {} -impl - TryFrom> - for types::RouterData +impl TryFrom> + for RouterData where F: NuveiPaymentsGenericResponse, { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: ResponseRouterData, ) -> Result { let redirection_data = match item.data.payment_method { - diesel_models::enums::PaymentMethod::Wallet - | diesel_models::enums::PaymentMethod::BankRedirect => item + enums::PaymentMethod::Wallet | enums::PaymentMethod::BankRedirect => item .response .payment_option .as_ref() .and_then(|po| po.redirect_url.clone()) - .map(|base_url| services::RedirectForm::from((base_url, services::Method::Get))), + .map(|base_url| RedirectForm::from((base_url, Method::Get))), _ => item .response .payment_option @@ -1579,9 +1599,9 @@ where .and_then(|o| o.card.clone()) .and_then(|card| card.three_d) .and_then(|three_ds| three_ds.acs_url.zip(three_ds.c_req)) - .map(|(base_url, creq)| services::RedirectForm::Form { + .map(|(base_url, creq)| RedirectForm::Form { endpoint: base_url, - method: services::Method::Post, + method: Method::Post, form_fields: std::collections::HashMap::from([( "creq".to_string(), creq.expose(), @@ -1595,18 +1615,18 @@ where response: if let Some(err) = build_error_response(&response, item.http_code) { err } else { - Ok(types::PaymentsResponseData::TransactionResponse { + Ok(PaymentsResponseData::TransactionResponse { resource_id: response .transaction_id .map_or(response.order_id.clone(), Some) // For paypal there will be no transaction_id, only order_id will be present - .map(types::ResponseId::ConnectorTransactionId) + .map(ResponseId::ConnectorTransactionId) .ok_or(errors::ConnectorError::MissingConnectorTransactionID)?, redirection_data: Box::new(redirection_data), mandate_reference: Box::new( response .payment_option .and_then(|po| po.user_payment_option_id) - .map(|id| types::MandateReference { + .map(|id| MandateReference { connector_mandate_id: Some(id), payment_method_id: None, mandate_metadata: None, @@ -1635,12 +1655,12 @@ where } } -impl TryFrom> +impl TryFrom> for types::PaymentsPreProcessingRouterData { type Error = error_stack::Report; fn try_from( - item: types::PaymentsPreprocessingResponseRouterData, + item: PaymentsPreprocessingResponseRouterData, ) -> Result { let response = item.response; let is_enrolled_for_3ds = response @@ -1649,11 +1669,11 @@ impl TryFrom for enums::RefundStatus { } } -impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: get_refund_response( @@ -1694,12 +1714,12 @@ impl TryFrom> - for types::RefundsRouterData +impl TryFrom> + for types::RefundsRouterData { type Error = error_stack::Report; fn try_from( - item: types::RefundsResponseRouterData, + item: RefundsResponseRouterData, ) -> Result { Ok(Self { response: get_refund_response( @@ -1714,15 +1734,12 @@ impl TryFrom } } -impl TryFrom<&types::RouterData> - for NuveiPaymentsRequest +impl TryFrom<&RouterData> for NuveiPaymentsRequest where Req: NuveiAuthorizePreprocessingCommon, { type Error = error_stack::Report; - fn try_from( - data: &types::RouterData, - ) -> Result { + fn try_from(data: &RouterData) -> Result { { let item = data; let connector_mandate_id = &item.request.get_connector_mandate_id(); @@ -1765,7 +1782,7 @@ fn get_refund_response( response: NuveiPaymentsResponse, http_code: u16, txn_id: String, -) -> Result { +) -> Result { let refund_status = response .transaction_status .clone() @@ -1779,7 +1796,7 @@ fn get_refund_response( Some(NuveiTransactionStatus::Error) => { get_error_response(response.gw_error_code, &response.gw_error_reason, http_code) } - _ => Ok(types::RefundsResponseData { + _ => Ok(RefundsResponseData { connector_refund_id: txn_id, refund_status, }), @@ -1791,14 +1808,14 @@ fn get_error_response( error_code: Option, error_msg: &Option, http_code: u16, -) -> Result { - Err(types::ErrorResponse { +) -> Result { + Err(ErrorResponse { code: error_code .map(|c| c.to_string()) - .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + .unwrap_or_else(|| NO_ERROR_CODE.to_string()), message: error_msg .clone() - .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + .unwrap_or_else(|| NO_ERROR_MESSAGE.to_string()), reason: None, status_code: http_code, attempt_status: None, diff --git a/crates/hyperswitch_connectors/src/constants.rs b/crates/hyperswitch_connectors/src/constants.rs index e83438d325b..b41dcf3422e 100644 --- a/crates/hyperswitch_connectors/src/constants.rs +++ b/crates/hyperswitch_connectors/src/constants.rs @@ -28,6 +28,8 @@ pub(crate) mod headers { pub(crate) const CORRELATION_ID: &str = "Correlation-Id"; pub(crate) const WP_API_VERSION: &str = "WP-Api-Version"; pub(crate) const SOURCE: &str = "Source"; + pub(crate) const USER_AGENT: &str = "User-Agent"; + pub(crate) const KEY: &str = "key"; } /// Unsupported response type error message diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index cfc65de64e8..f34b09e8f35 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -95,6 +95,7 @@ macro_rules! default_imp_for_authorize_session_token { } default_imp_for_authorize_session_token!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -103,6 +104,7 @@ default_imp_for_authorize_session_token!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -119,11 +121,15 @@ default_imp_for_authorize_session_token!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, @@ -134,6 +140,7 @@ default_imp_for_authorize_session_token!( connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -170,6 +177,7 @@ macro_rules! default_imp_for_calculate_tax { } default_imp_for_calculate_tax!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -178,6 +186,7 @@ default_imp_for_calculate_tax!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -194,11 +203,16 @@ default_imp_for_calculate_tax!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Nexinets, @@ -206,6 +220,7 @@ default_imp_for_calculate_tax!( connectors::Paybox, connectors::Nomupay, connectors::Novalnet, + connectors::Nuvei, connectors::Payeezy, connectors::Payu, connectors::Placetopay, @@ -245,6 +260,7 @@ macro_rules! default_imp_for_session_update { } default_imp_for_session_update!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -253,6 +269,7 @@ default_imp_for_session_update!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -268,8 +285,11 @@ default_imp_for_session_update!( connectors::Fiservemea, connectors::Forte, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Rapyd, connectors::Razorpay, connectors::Redsys, @@ -277,18 +297,21 @@ default_imp_for_session_update!( connectors::Stax, connectors::Square, connectors::Taxjar, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::UnifiedAuthenticationService, connectors::Fiuu, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Worldline, @@ -321,6 +344,7 @@ macro_rules! default_imp_for_post_session_tokens { } default_imp_for_post_session_tokens!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -328,6 +352,7 @@ default_imp_for_post_session_tokens!( connectors::Bankofamerica, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Billwerk, connectors::Cashtocode, @@ -345,25 +370,31 @@ default_imp_for_post_session_tokens!( connectors::Fiservemea, connectors::Forte, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Rapyd, connectors::Razorpay, connectors::Redsys, connectors::Shift4, connectors::Stax, connectors::Taxjar, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Fiuu, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Worldline, @@ -399,6 +430,7 @@ macro_rules! default_imp_for_complete_authorize { } default_imp_for_complete_authorize!( + connectors::Aci, connectors::Amazonpay, connectors::Bamboraapac, connectors::Bankofamerica, @@ -420,8 +452,12 @@ default_imp_for_complete_authorize!( connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, + connectors::Mifinity, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, @@ -463,6 +499,7 @@ macro_rules! default_imp_for_incremental_authorization { } default_imp_for_incremental_authorization!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -471,6 +508,7 @@ default_imp_for_incremental_authorization!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -486,21 +524,27 @@ default_imp_for_incremental_authorization!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -538,6 +582,7 @@ macro_rules! default_imp_for_create_customer { } default_imp_for_create_customer!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -546,6 +591,7 @@ default_imp_for_create_customer!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -562,16 +608,22 @@ default_imp_for_create_customer!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, @@ -615,12 +667,13 @@ macro_rules! default_imp_for_connector_redirect_response { } default_imp_for_connector_redirect_response!( + connectors::Aci, connectors::Amazonpay, connectors::Billwerk, connectors::Bitpay, - connectors::Boku, connectors::Bamboraapac, connectors::Bankofamerica, + connectors::Boku, connectors::Cashtocode, connectors::Chargebee, connectors::Coinbase, @@ -638,8 +691,12 @@ default_imp_for_connector_redirect_response!( connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, + connectors::Mifinity, connectors::Multisafepay, connectors::Nexinets, connectors::Nexixpay, @@ -682,6 +739,7 @@ macro_rules! default_imp_for_pre_processing_steps{ } default_imp_for_pre_processing_steps!( + connectors::Aci, connectors::Amazonpay, connectors::Bambora, connectors::Bamboraapac, @@ -689,6 +747,7 @@ default_imp_for_pre_processing_steps!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -704,10 +763,14 @@ default_imp_for_pre_processing_steps!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, @@ -717,6 +780,7 @@ default_imp_for_pre_processing_steps!( connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -754,6 +818,7 @@ macro_rules! default_imp_for_post_processing_steps{ } default_imp_for_post_processing_steps!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -762,6 +827,7 @@ default_imp_for_post_processing_steps!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -778,21 +844,27 @@ default_imp_for_post_processing_steps!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -831,6 +903,7 @@ macro_rules! default_imp_for_approve { } default_imp_for_approve!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -839,6 +912,7 @@ default_imp_for_approve!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -855,21 +929,27 @@ default_imp_for_approve!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -908,6 +988,7 @@ macro_rules! default_imp_for_reject { } default_imp_for_reject!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -916,6 +997,7 @@ default_imp_for_reject!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -932,21 +1014,27 @@ default_imp_for_reject!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -985,6 +1073,7 @@ macro_rules! default_imp_for_webhook_source_verification { } default_imp_for_webhook_source_verification!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -993,6 +1082,7 @@ default_imp_for_webhook_source_verification!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1009,21 +1099,27 @@ default_imp_for_webhook_source_verification!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1063,6 +1159,7 @@ macro_rules! default_imp_for_accept_dispute { } default_imp_for_accept_dispute!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1071,6 +1168,7 @@ default_imp_for_accept_dispute!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1087,21 +1185,27 @@ default_imp_for_accept_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1140,6 +1244,7 @@ macro_rules! default_imp_for_submit_evidence { } default_imp_for_submit_evidence!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1148,6 +1253,7 @@ default_imp_for_submit_evidence!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1164,21 +1270,27 @@ default_imp_for_submit_evidence!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1217,6 +1329,7 @@ macro_rules! default_imp_for_defend_dispute { } default_imp_for_defend_dispute!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1225,6 +1338,7 @@ default_imp_for_defend_dispute!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1241,21 +1355,27 @@ default_imp_for_defend_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Helcim, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1303,6 +1423,7 @@ macro_rules! default_imp_for_file_upload { } default_imp_for_file_upload!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1311,6 +1432,7 @@ default_imp_for_file_upload!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1327,21 +1449,27 @@ default_imp_for_file_upload!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1373,6 +1501,7 @@ macro_rules! default_imp_for_payouts { } default_imp_for_payouts!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1381,6 +1510,7 @@ default_imp_for_payouts!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1396,11 +1526,16 @@ default_imp_for_payouts!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Nexinets, @@ -1408,6 +1543,7 @@ default_imp_for_payouts!( connectors::Paybox, connectors::Nomupay, connectors::Novalnet, + connectors::Nuvei, connectors::Payeezy, connectors::Payu, connectors::Placetopay, @@ -1450,6 +1586,7 @@ macro_rules! default_imp_for_payouts_create { #[cfg(feature = "payouts")] default_imp_for_payouts_create!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1458,6 +1595,7 @@ default_imp_for_payouts_create!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1474,21 +1612,27 @@ default_imp_for_payouts_create!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1529,6 +1673,7 @@ macro_rules! default_imp_for_payouts_retrieve { #[cfg(feature = "payouts")] default_imp_for_payouts_retrieve!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1537,6 +1682,7 @@ default_imp_for_payouts_retrieve!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1553,21 +1699,27 @@ default_imp_for_payouts_retrieve!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1608,6 +1760,7 @@ macro_rules! default_imp_for_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_payouts_eligibility!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1616,6 +1769,7 @@ default_imp_for_payouts_eligibility!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1632,21 +1786,27 @@ default_imp_for_payouts_eligibility!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1687,6 +1847,7 @@ macro_rules! default_imp_for_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_payouts_fulfill!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1695,6 +1856,7 @@ default_imp_for_payouts_fulfill!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1710,21 +1872,27 @@ default_imp_for_payouts_fulfill!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1765,6 +1933,7 @@ macro_rules! default_imp_for_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_payouts_cancel!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1773,6 +1942,7 @@ default_imp_for_payouts_cancel!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1789,21 +1959,27 @@ default_imp_for_payouts_cancel!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1844,6 +2020,7 @@ macro_rules! default_imp_for_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_payouts_quote!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1852,6 +2029,7 @@ default_imp_for_payouts_quote!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1868,21 +2046,27 @@ default_imp_for_payouts_quote!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1923,6 +2107,7 @@ macro_rules! default_imp_for_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1931,6 +2116,7 @@ default_imp_for_payouts_recipient!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1947,21 +2133,27 @@ default_imp_for_payouts_recipient!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2002,6 +2194,7 @@ macro_rules! default_imp_for_payouts_recipient_account { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient_account!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2010,6 +2203,7 @@ default_imp_for_payouts_recipient_account!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2026,21 +2220,27 @@ default_imp_for_payouts_recipient_account!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2081,6 +2281,7 @@ macro_rules! default_imp_for_frm_sale { #[cfg(feature = "frm")] default_imp_for_frm_sale!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2089,6 +2290,7 @@ default_imp_for_frm_sale!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2105,21 +2307,27 @@ default_imp_for_frm_sale!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2160,6 +2368,7 @@ macro_rules! default_imp_for_frm_checkout { #[cfg(feature = "frm")] default_imp_for_frm_checkout!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2168,6 +2377,7 @@ default_imp_for_frm_checkout!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2184,21 +2394,27 @@ default_imp_for_frm_checkout!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2239,6 +2455,7 @@ macro_rules! default_imp_for_frm_transaction { #[cfg(feature = "frm")] default_imp_for_frm_transaction!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2247,6 +2464,7 @@ default_imp_for_frm_transaction!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2263,21 +2481,27 @@ default_imp_for_frm_transaction!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2318,6 +2542,7 @@ macro_rules! default_imp_for_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_frm_fulfillment!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2326,6 +2551,7 @@ default_imp_for_frm_fulfillment!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2342,21 +2568,27 @@ default_imp_for_frm_fulfillment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2397,6 +2629,7 @@ macro_rules! default_imp_for_frm_record_return { #[cfg(feature = "frm")] default_imp_for_frm_record_return!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2405,6 +2638,7 @@ default_imp_for_frm_record_return!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2421,21 +2655,27 @@ default_imp_for_frm_record_return!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2473,6 +2713,7 @@ macro_rules! default_imp_for_revoking_mandates { } default_imp_for_revoking_mandates!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2481,6 +2722,7 @@ default_imp_for_revoking_mandates!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2496,21 +2738,27 @@ default_imp_for_revoking_mandates!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -2548,6 +2796,7 @@ macro_rules! default_imp_for_uas_pre_authentication { } default_imp_for_uas_pre_authentication!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2556,6 +2805,7 @@ default_imp_for_uas_pre_authentication!( connectors::Billwerk, connectors::Bluesnap, connectors::Bitpay, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2573,19 +2823,25 @@ default_imp_for_uas_pre_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Payeezy, connectors::Payu, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Paybox, @@ -2623,6 +2879,7 @@ macro_rules! default_imp_for_uas_post_authentication { } default_imp_for_uas_post_authentication!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2631,6 +2888,7 @@ default_imp_for_uas_post_authentication!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2648,19 +2906,25 @@ default_imp_for_uas_post_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Payeezy, connectors::Payu, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Paybox, @@ -2698,6 +2962,7 @@ macro_rules! default_imp_for_uas_authentication { } default_imp_for_uas_authentication!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2706,6 +2971,7 @@ default_imp_for_uas_authentication!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -2723,19 +2989,25 @@ default_imp_for_uas_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Payeezy, connectors::Payu, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Paybox, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 51607778eca..d0241967acb 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -213,6 +213,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -230,21 +231,27 @@ default_imp_for_new_connector_integration_payment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -283,6 +290,7 @@ macro_rules! default_imp_for_new_connector_integration_refund { } default_imp_for_new_connector_integration_refund!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -291,6 +299,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -308,21 +317,27 @@ default_imp_for_new_connector_integration_refund!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -356,6 +371,7 @@ macro_rules! default_imp_for_new_connector_integration_connector_access_token { } default_imp_for_new_connector_integration_connector_access_token!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -364,6 +380,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -381,21 +398,27 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -435,6 +458,7 @@ macro_rules! default_imp_for_new_connector_integration_accept_dispute { } default_imp_for_new_connector_integration_accept_dispute!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -443,6 +467,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -459,21 +484,27 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -512,6 +543,7 @@ macro_rules! default_imp_for_new_connector_integration_submit_evidence { } default_imp_for_new_connector_integration_submit_evidence!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -520,6 +552,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -536,21 +569,27 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -589,6 +628,7 @@ macro_rules! default_imp_for_new_connector_integration_defend_dispute { } default_imp_for_new_connector_integration_defend_dispute!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -597,6 +637,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -614,21 +655,27 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -677,6 +724,7 @@ macro_rules! default_imp_for_new_connector_integration_file_upload { } default_imp_for_new_connector_integration_file_upload!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -685,6 +733,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -702,21 +751,27 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -757,6 +812,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_create { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_create!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -765,6 +821,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -782,21 +839,27 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -837,6 +900,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_eligibility!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -845,6 +909,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -862,21 +927,27 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -917,6 +988,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_fulfill!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -925,6 +997,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -942,21 +1015,27 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -997,6 +1076,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_cancel!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1005,6 +1085,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1022,21 +1103,27 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1077,6 +1164,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_quote!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1085,6 +1173,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1102,21 +1191,27 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1157,6 +1252,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1165,6 +1261,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1182,21 +1279,27 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1237,6 +1340,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_sync { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_sync!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1245,6 +1349,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1262,21 +1367,27 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1317,6 +1428,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient_account #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient_account!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1325,6 +1437,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1342,21 +1455,27 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1395,6 +1514,7 @@ macro_rules! default_imp_for_new_connector_integration_webhook_source_verificati } default_imp_for_new_connector_integration_webhook_source_verification!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1403,6 +1523,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1420,21 +1541,27 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1475,6 +1602,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_sale { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_sale!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1483,6 +1611,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1500,21 +1629,27 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1555,6 +1690,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_checkout { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_checkout!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1563,6 +1699,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1580,21 +1717,27 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1635,6 +1778,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_transaction { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_transaction!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1643,6 +1787,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1660,21 +1805,27 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1715,6 +1866,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_fulfillment!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1723,6 +1875,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1740,21 +1893,27 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1795,6 +1954,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_record_return { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_record_return!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1803,6 +1963,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1820,21 +1981,27 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, @@ -1872,6 +2039,7 @@ macro_rules! default_imp_for_new_connector_integration_revoking_mandates { } default_imp_for_new_connector_integration_revoking_mandates!( + connectors::Aci, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -1880,6 +2048,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Billwerk, connectors::Bitpay, connectors::Bluesnap, + connectors::Braintree, connectors::Boku, connectors::Cashtocode, connectors::Chargebee, @@ -1897,21 +2066,27 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Globalpay, connectors::Globepay, connectors::Gocardless, connectors::Helcim, + connectors::Iatapay, connectors::Inespay, + connectors::Itaubank, connectors::Jpmorgan, + connectors::Klarna, connectors::Nomupay, connectors::Novalnet, connectors::Nexinets, connectors::Nexixpay, + connectors::Nuvei, connectors::Paybox, connectors::Payeezy, connectors::Payu, connectors::Placetopay, connectors::Powertranz, connectors::Prophetpay, + connectors::Mifinity, connectors::Mollie, connectors::Multisafepay, connectors::Rapyd, diff --git a/crates/hyperswitch_connectors/src/types.rs b/crates/hyperswitch_connectors/src/types.rs index f731450f386..e893238fd8d 100644 --- a/crates/hyperswitch_connectors/src/types.rs +++ b/crates/hyperswitch_connectors/src/types.rs @@ -2,17 +2,18 @@ use hyperswitch_domain_models::types::{PayoutsData, PayoutsResponseData}; use hyperswitch_domain_models::{ router_data::{AccessToken, RouterData}, - router_flow_types::{AccessTokenAuth, Capture, PSync, PreProcessing, Session, Void}, + router_flow_types::{AccessTokenAuth, Authorize, Capture, PSync, PreProcessing, Session, Void}, router_request_types::{ - AccessTokenRequestData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, - PaymentsSessionData, PaymentsSyncData, RefundsData, + AccessTokenRequestData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, + PaymentsPreProcessingData, PaymentsSessionData, PaymentsSyncData, RefundsData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, }; pub(crate) type PaymentsSyncResponseRouterData = ResponseRouterData; - +pub(crate) type PaymentsResponseRouterData = + ResponseRouterData; pub(crate) type PaymentsCaptureResponseRouterData = ResponseRouterData; pub(crate) type RefundsResponseRouterData = diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 5f9820070c1..aec4d31072d 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -10,7 +10,7 @@ use common_enums::{ }; use common_utils::{ consts::BASE64_ENGINE, - errors::{CustomResult, ReportSwitchExt}, + errors::{CustomResult, ParsingError, ReportSwitchExt}, ext_traits::{OptionExt, StringExt, ValueExt}, id_type, pii::{self, Email, IpAddress}, @@ -29,6 +29,7 @@ use hyperswitch_domain_models::{ PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSyncData, RefundsData, ResponseId, SetupMandateRequestData, }, + router_response_types::CaptureSyncResponse, types::OrderDetailsWithAmount, }; use hyperswitch_interfaces::{api, consts, errors, types::Response}; @@ -39,6 +40,7 @@ use regex::Regex; use router_env::logger; use serde::Serializer; use serde_json::Value; +use time::PrimitiveDateTime; use crate::{constants::UNSUPPORTED_ERROR_MESSAGE, types::RefreshTokenRouterData}; @@ -64,6 +66,11 @@ pub(crate) fn to_currency_base_unit_with_zero_decimal_check( .change_context(errors::ConnectorError::RequestEncodingFailed) } +pub(crate) fn get_timestamp_in_milliseconds(datetime: &PrimitiveDateTime) -> i64 { + let utc_datetime = datetime.assume_utc(); + utc_datetime.unix_timestamp() * 1000 +} + pub(crate) fn get_amount_as_string( currency_unit: &api::CurrencyUnit, amount: i64, @@ -76,6 +83,12 @@ pub(crate) fn get_amount_as_string( Ok(amount) } +pub(crate) fn base64_decode(data: String) -> Result, Error> { + BASE64_ENGINE + .decode(data) + .change_context(errors::ConnectorError::ResponseDeserializationFailed) +} + pub(crate) fn to_currency_base_unit( amount: i64, currency: enums::Currency, @@ -141,6 +154,47 @@ pub(crate) fn get_error_code_error_message_based_on_priority( .cloned() } +pub trait MultipleCaptureSyncResponse { + fn get_connector_capture_id(&self) -> String; + fn get_capture_attempt_status(&self) -> AttemptStatus; + fn is_capture_response(&self) -> bool; + fn get_connector_reference_id(&self) -> Option { + None + } + fn get_amount_captured(&self) -> Result, error_stack::Report>; +} + +pub(crate) fn construct_captures_response_hashmap( + capture_sync_response_list: Vec, +) -> CustomResult, errors::ConnectorError> +where + T: MultipleCaptureSyncResponse, +{ + let mut hashmap = HashMap::new(); + for capture_sync_response in capture_sync_response_list { + let connector_capture_id = capture_sync_response.get_connector_capture_id(); + if capture_sync_response.is_capture_response() { + hashmap.insert( + connector_capture_id.clone(), + CaptureSyncResponse::Success { + resource_id: ResponseId::ConnectorTransactionId(connector_capture_id), + status: capture_sync_response.get_capture_attempt_status(), + connector_response_reference_id: capture_sync_response + .get_connector_reference_id(), + amount: capture_sync_response + .get_amount_captured() + .change_context(errors::ConnectorError::AmountConversionFailed) + .attach_printable( + "failed to convert back captured response amount to minor unit", + )?, + }, + ); + } + } + + Ok(hashmap) +} + #[derive(Clone, Debug, serde::Serialize)] #[serde(rename_all = "camelCase")] pub struct GooglePayWalletData { @@ -305,6 +359,24 @@ pub(crate) fn convert_amount( .change_context(errors::ConnectorError::AmountConversionFailed) } +pub(crate) fn validate_currency( + request_currency: enums::Currency, + merchant_config_currency: Option, +) -> Result<(), errors::ConnectorError> { + let merchant_config_currency = + merchant_config_currency.ok_or(errors::ConnectorError::NoConnectorMetaData)?; + if request_currency != merchant_config_currency { + Err(errors::ConnectorError::NotSupported { + message: format!( + "currency {} is not supported for this merchant account", + request_currency + ), + connector: "Braintree", + })? + } + Ok(()) +} + pub(crate) fn convert_back_amount_to_minor_units( amount_convertor: &dyn AmountConvertor, amount: T, @@ -1703,6 +1775,7 @@ pub trait RefundsRequestData { fn get_connector_refund_id(&self) -> Result; fn get_webhook_url(&self) -> Result; fn get_browser_info(&self) -> Result; + fn get_connector_metadata(&self) -> Result; } impl RefundsRequestData for RefundsData { @@ -1728,6 +1801,11 @@ impl RefundsRequestData for RefundsData { .clone() .and_then(|browser_info| browser_info.language) } + fn get_connector_metadata(&self) -> Result { + self.connector_metadata + .clone() + .ok_or_else(missing_field_err("connector_metadata")) + } } pub trait PaymentsSetupMandateRequestData { @@ -1855,6 +1933,8 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { } pub trait AddressData { fn get_optional_full_name(&self) -> Option>; + fn get_email(&self) -> Result; + fn get_phone_with_country_code(&self) -> Result, Error>; } impl AddressData for Address { @@ -1863,6 +1943,18 @@ impl AddressData for Address { .as_ref() .and_then(|billing_address| billing_address.get_optional_full_name()) } + + fn get_email(&self) -> Result { + self.email.clone().ok_or_else(missing_field_err("email")) + } + + fn get_phone_with_country_code(&self) -> Result, Error> { + self.phone + .clone() + .map(|phone_details| phone_details.get_number_with_country_code()) + .transpose()? + .ok_or_else(missing_field_err("phone")) + } } pub trait PaymentsPreProcessingRequestData { fn get_redirect_response_payload(&self) -> Result; diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index a8cdf2b6cf8..e6767fbb08b 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -1,22 +1,14 @@ -pub mod aci; pub mod adyen; pub mod adyenplatform; pub mod authorizedotnet; -pub mod braintree; pub mod checkout; #[cfg(feature = "dummy_connector")] pub mod dummyconnector; pub mod ebanx; -pub mod globalpay; pub mod gpayments; -pub mod iatapay; -pub mod itaubank; -pub mod klarna; -pub mod mifinity; pub mod netcetera; pub mod nmi; pub mod noon; -pub mod nuvei; pub mod opayo; pub mod opennode; pub mod payme; @@ -33,36 +25,39 @@ pub mod wellsfargopayout; pub mod wise; pub use hyperswitch_connectors::connectors::{ - airwallex, airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, bambora, bambora::Bambora, - bamboraapac, bamboraapac::Bamboraapac, bankofamerica, bankofamerica::Bankofamerica, billwerk, - billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, bluesnap::Bluesnap, boku, boku::Boku, - cashtocode, cashtocode::Cashtocode, chargebee::Chargebee, coinbase, coinbase::Coinbase, - coingate, coingate::Coingate, cryptopay, cryptopay::Cryptopay, ctp_mastercard, + aci, aci::Aci, airwallex, airwallex::Airwallex, amazonpay, amazonpay::Amazonpay, bambora, + bambora::Bambora, bamboraapac, bamboraapac::Bamboraapac, bankofamerica, + bankofamerica::Bankofamerica, billwerk, billwerk::Billwerk, bitpay, bitpay::Bitpay, bluesnap, + bluesnap::Bluesnap, boku, boku::Boku, braintree, braintree::Braintree, cashtocode, + cashtocode::Cashtocode, chargebee::Chargebee, coinbase, coinbase::Coinbase, coingate, + coingate::Coingate, cryptopay, cryptopay::Cryptopay, ctp_mastercard, ctp_mastercard::CtpMastercard, cybersource, cybersource::Cybersource, datatrans, datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, fiuu::Fiuu, forte, forte::Forte, - globepay, globepay::Globepay, gocardless, gocardless::Gocardless, helcim, helcim::Helcim, - inespay, inespay::Inespay, jpmorgan, jpmorgan::Jpmorgan, mollie, mollie::Mollie, multisafepay, + globalpay, globalpay::Globalpay, globepay, globepay::Globepay, gocardless, + gocardless::Gocardless, helcim, helcim::Helcim, iatapay, iatapay::Iatapay, inespay, + inespay::Inespay, itaubank, itaubank::Itaubank, jpmorgan, jpmorgan::Jpmorgan, klarna, + klarna::Klarna, mifinity, mifinity::Mifinity, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, - nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, paybox, paybox::Paybox, payeezy, - payeezy::Payeezy, payu, payu::Payu, placetopay, placetopay::Placetopay, powertranz, - powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, - razorpay::Razorpay, redsys, redsys::Redsys, shift4, shift4::Shift4, square, square::Square, - stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, thunes::Thunes, tsys, tsys::Tsys, - unified_authentication_service, unified_authentication_service::UnifiedAuthenticationService, - volt, volt::Volt, wellsfargo, wellsfargo::Wellsfargo, worldline, worldline::Worldline, - worldpay, worldpay::Worldpay, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, + nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, nuvei, nuvei::Nuvei, paybox, + paybox::Paybox, payeezy, payeezy::Payeezy, payu, payu::Payu, placetopay, + placetopay::Placetopay, powertranz, powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, + rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, redsys, redsys::Redsys, shift4, + shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, + thunes::Thunes, tsys, tsys::Tsys, unified_authentication_service, + unified_authentication_service::UnifiedAuthenticationService, volt, volt::Volt, wellsfargo, + wellsfargo::Wellsfargo, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, xendit, + xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, }; #[cfg(feature = "dummy_connector")] pub use self::dummyconnector::DummyConnector; pub use self::{ - aci::Aci, adyen::Adyen, adyenplatform::Adyenplatform, authorizedotnet::Authorizedotnet, - braintree::Braintree, checkout::Checkout, ebanx::Ebanx, globalpay::Globalpay, - gpayments::Gpayments, iatapay::Iatapay, itaubank::Itaubank, klarna::Klarna, mifinity::Mifinity, - netcetera::Netcetera, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, - payme::Payme, payone::Payone, paypal::Paypal, plaid::Plaid, riskified::Riskified, - signifyd::Signifyd, stripe::Stripe, threedsecureio::Threedsecureio, trustpay::Trustpay, - wellsfargopayout::Wellsfargopayout, wise::Wise, + adyen::Adyen, adyenplatform::Adyenplatform, authorizedotnet::Authorizedotnet, + checkout::Checkout, ebanx::Ebanx, gpayments::Gpayments, netcetera::Netcetera, nmi::Nmi, + noon::Noon, opayo::Opayo, opennode::Opennode, payme::Payme, payone::Payone, paypal::Paypal, + plaid::Plaid, riskified::Riskified, signifyd::Signifyd, stripe::Stripe, + threedsecureio::Threedsecureio, trustpay::Trustpay, wellsfargopayout::Wellsfargopayout, + wise::Wise, }; diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index d27ad5d6019..4d506449589 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -42,7 +42,6 @@ pub const DEFAULT_LIST_API_LIMIT: u16 = 10; // String literals pub(crate) const UNSUPPORTED_ERROR_MESSAGE: &str = "Unsupported response type"; pub(crate) const LOW_BALANCE_ERROR_MESSAGE: &str = "Insufficient balance in the payment method"; -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."; diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 5263ec9fb28..7a43ec1a189 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -698,23 +698,15 @@ macro_rules! default_imp_for_new_connector_integration_payment { } default_imp_for_new_connector_integration_payment!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -747,23 +739,15 @@ macro_rules! default_imp_for_new_connector_integration_refund { } default_imp_for_new_connector_integration_refund!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -790,23 +774,15 @@ macro_rules! default_imp_for_new_connector_integration_connector_access_token { } default_imp_for_new_connector_integration_connector_access_token!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -855,23 +831,15 @@ macro_rules! default_imp_for_new_connector_integration_submit_evidence { } default_imp_for_new_connector_integration_accept_dispute!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -902,23 +870,15 @@ macro_rules! default_imp_for_new_connector_integration_defend_dispute { }; } default_imp_for_new_connector_integration_defend_dispute!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -933,23 +893,15 @@ default_imp_for_new_connector_integration_defend_dispute!( connector::Plaid ); default_imp_for_new_connector_integration_submit_evidence!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -991,23 +943,15 @@ macro_rules! default_imp_for_new_connector_integration_file_upload { } default_imp_for_new_connector_integration_file_upload!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1077,7 +1021,6 @@ default_imp_for_new_connector_integration_payouts!( connector::Nomupay, connector::Noon, connector::Novalnet, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Paybox, @@ -1130,23 +1073,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_create { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_create!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1180,23 +1115,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_eligibility!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1230,23 +1157,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_fulfill!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1280,23 +1199,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_cancel!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1330,23 +1241,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_quote!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1380,23 +1283,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1431,22 +1326,14 @@ macro_rules! default_imp_for_new_connector_integration_payouts_sync { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_sync!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1480,23 +1367,15 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient_account #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient_account!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1528,23 +1407,15 @@ macro_rules! default_imp_for_new_connector_integration_webhook_source_verificati } default_imp_for_new_connector_integration_webhook_source_verification!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1614,7 +1485,6 @@ default_imp_for_new_connector_integration_frm!( connector::Nomupay, connector::Noon, connector::Novalnet, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Paybox, @@ -1667,23 +1537,15 @@ macro_rules! default_imp_for_new_connector_integration_frm_sale { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_sale!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1717,23 +1579,15 @@ macro_rules! default_imp_for_new_connector_integration_frm_checkout { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_checkout!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1767,23 +1621,15 @@ macro_rules! default_imp_for_new_connector_integration_frm_transaction { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_transaction!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1817,23 +1663,15 @@ macro_rules! default_imp_for_new_connector_integration_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_fulfillment!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1867,23 +1705,15 @@ macro_rules! default_imp_for_new_connector_integration_frm_record_return { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_record_return!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1914,23 +1744,15 @@ macro_rules! default_imp_for_new_connector_integration_revoking_mandates { } default_imp_for_new_connector_integration_revoking_mandates!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -2110,22 +1932,14 @@ macro_rules! default_imp_for_new_connector_integration_uas { default_imp_for_new_connector_integration_uas!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 852b5d95209..6728d51558c 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -214,15 +214,10 @@ impl default_imp_for_complete_authorize!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Checkout, connector::Ebanx, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Noon, connector::Opayo, @@ -265,22 +260,14 @@ impl } default_imp_for_webhook_source_verification!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -324,22 +311,14 @@ impl default_imp_for_create_customer!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -385,14 +364,9 @@ impl services::ConnectorRedirectResponse for connector::DummyConnec default_imp_for_connector_redirect_response!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Ebanx, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Opayo, connector::Opennode, @@ -537,20 +511,12 @@ impl default_imp_for_accept_dispute!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -617,20 +583,12 @@ impl default_imp_for_file_upload!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Payme, connector::Payone, @@ -674,20 +632,12 @@ impl default_imp_for_submit_evidence!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Payme, connector::Payone, @@ -731,20 +681,12 @@ impl default_imp_for_defend_dispute!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Payme, connector::Payone, @@ -804,17 +746,10 @@ impl default_imp_for_pre_processing_steps!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Iatapay, - connector::Itaubank, - connector::Globalpay, connector::Gpayments, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Noon, connector::Opayo, @@ -844,22 +779,14 @@ default_imp_for_post_processing_steps!( connector::Adyenplatform, connector::Adyen, connector::Nmi, - connector::Nuvei, connector::Payme, connector::Paypal, connector::Stripe, connector::Trustpay, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Iatapay, - connector::Itaubank, - connector::Globalpay, connector::Gpayments, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Noon, connector::Opayo, @@ -884,20 +811,12 @@ macro_rules! default_imp_for_payouts { impl Payouts for connector::DummyConnector {} default_imp_for_payouts!( - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -939,20 +858,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_create!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -995,22 +906,14 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_retrieve!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1058,20 +961,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_eligibility!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1115,20 +1010,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_fulfill!( - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1170,20 +1057,12 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_cancel!( connector::Adyenplatform, - connector::Aci, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1227,21 +1106,13 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_quote!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1286,21 +1157,13 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_recipient!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1347,22 +1210,14 @@ impl #[cfg(feature = "payouts")] default_imp_for_payouts_recipient_account!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1406,22 +1261,14 @@ impl default_imp_for_approve!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1466,22 +1313,14 @@ impl default_imp_for_reject!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1628,22 +1467,14 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_sale!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1688,22 +1519,14 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_checkout!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1748,22 +1571,14 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_transaction!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1808,22 +1623,14 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_fulfillment!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1868,22 +1675,14 @@ impl #[cfg(feature = "frm")] default_imp_for_frm_record_return!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1926,22 +1725,14 @@ impl default_imp_for_incremental_authorization!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -1984,21 +1775,13 @@ impl } default_imp_for_revoking_mandates!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -2212,19 +1995,12 @@ impl { } default_imp_for_authorize_session_token!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, @@ -2269,21 +2045,13 @@ impl } default_imp_for_calculate_tax!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, - connector::Nuvei, connector::Nmi, connector::Noon, connector::Opayo, @@ -2327,21 +2095,13 @@ impl } default_imp_for_session_update!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, - connector::Nuvei, connector::Nmi, connector::Noon, connector::Opayo, @@ -2384,21 +2144,13 @@ impl } default_imp_for_post_session_tokens!( - connector::Aci, connector::Adyen, connector::Adyenplatform, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, - connector::Nuvei, connector::Nmi, connector::Noon, connector::Opayo, @@ -2445,22 +2197,14 @@ impl default_imp_for_uas_pre_authentication!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -2503,22 +2247,14 @@ impl default_imp_for_uas_post_authentication!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, @@ -2561,22 +2297,14 @@ impl default_imp_for_uas_authentication!( connector::Adyenplatform, - connector::Aci, connector::Adyen, connector::Authorizedotnet, - connector::Braintree, connector::Checkout, connector::Ebanx, - connector::Globalpay, connector::Gpayments, - connector::Iatapay, - connector::Itaubank, - connector::Klarna, - connector::Mifinity, connector::Netcetera, connector::Nmi, connector::Noon, - connector::Nuvei, connector::Opayo, connector::Opennode, connector::Payme, From df328c5e520b89b09e1b684d039f1d9613d78613 Mon Sep 17 00:00:00 2001 From: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Date: Fri, 7 Feb 2025 17:10:12 +0530 Subject: [PATCH 067/133] feat(opensearch): add amount and customer_id as filters and handle name for different indexes (#7073) --- crates/analytics/src/opensearch.rs | 48 ++++-- crates/analytics/src/search.rs | 175 +++++++++++++++------- crates/api_models/src/analytics/search.rs | 4 + 3 files changed, 162 insertions(+), 65 deletions(-) diff --git a/crates/analytics/src/opensearch.rs b/crates/analytics/src/opensearch.rs index e85340e49d2..f12198d0054 100644 --- a/crates/analytics/src/opensearch.rs +++ b/crates/analytics/src/opensearch.rs @@ -456,7 +456,7 @@ pub struct OpenSearchQueryBuilder { pub query: String, pub offset: Option, pub count: Option, - pub filters: Vec<(String, Vec)>, + pub filters: Vec<(String, Vec)>, pub time_range: Option, search_params: Vec, case_sensitive_fields: HashSet<&'static str>, @@ -477,6 +477,8 @@ impl OpenSearchQueryBuilder { "search_tags.keyword", "card_last_4.keyword", "payment_id.keyword", + "amount", + "customer_id.keyword", ]), } } @@ -492,7 +494,7 @@ impl OpenSearchQueryBuilder { Ok(()) } - pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec) -> QueryResult<()> { + pub fn add_filter_clause(&mut self, lhs: String, rhs: Vec) -> QueryResult<()> { self.filters.push((lhs, rhs)); Ok(()) } @@ -505,9 +507,18 @@ impl OpenSearchQueryBuilder { } } + pub fn get_amount_field(&self, index: SearchIndex) -> &str { + match index { + SearchIndex::Refunds | SearchIndex::SessionizerRefunds => "refund_amount", + SearchIndex::Disputes | SearchIndex::SessionizerDisputes => "dispute_amount", + _ => "amount", + } + } + pub fn build_filter_array( &self, - case_sensitive_filters: Vec<&(String, Vec)>, + case_sensitive_filters: Vec<&(String, Vec)>, + index: SearchIndex, ) -> Vec { let mut filter_array = Vec::new(); if !self.query.is_empty() { @@ -522,7 +533,14 @@ impl OpenSearchQueryBuilder { let case_sensitive_json_filters = case_sensitive_filters .into_iter() - .map(|(k, v)| json!({"terms": {k: v}})) + .map(|(k, v)| { + let key = if *k == "amount" { + self.get_amount_field(index).to_string() + } else { + k.clone() + }; + json!({"terms": {key: v}}) + }) .collect::>(); filter_array.extend(case_sensitive_json_filters); @@ -542,7 +560,7 @@ impl OpenSearchQueryBuilder { pub fn build_case_insensitive_filters( &self, mut payload: Value, - case_insensitive_filters: &[&(String, Vec)], + case_insensitive_filters: &[&(String, Vec)], auth_array: Vec, index: SearchIndex, ) -> Value { @@ -690,22 +708,16 @@ impl OpenSearchQueryBuilder { /// Ensure that the input data and the structure of the query are valid and correctly handled. pub fn construct_payload(&self, indexes: &[SearchIndex]) -> QueryResult> { let mut query_obj = Map::new(); - let mut bool_obj = Map::new(); + let bool_obj = Map::new(); let (case_sensitive_filters, case_insensitive_filters): (Vec<_>, Vec<_>) = self .filters .iter() .partition(|(k, _)| self.case_sensitive_fields.contains(k.as_str())); - let filter_array = self.build_filter_array(case_sensitive_filters); - - if !filter_array.is_empty() { - bool_obj.insert("filter".to_string(), Value::Array(filter_array)); - } - let should_array = self.build_auth_array(); - query_obj.insert("bool".to_string(), Value::Object(bool_obj)); + query_obj.insert("bool".to_string(), Value::Object(bool_obj.clone())); let mut sort_obj = Map::new(); sort_obj.insert( @@ -724,6 +736,16 @@ impl OpenSearchQueryBuilder { Value::Object(sort_obj.clone()) ] }); + let filter_array = self.build_filter_array(case_sensitive_filters.clone(), *index); + if !filter_array.is_empty() { + payload + .get_mut("query") + .and_then(|query| query.get_mut("bool")) + .and_then(|bool_obj| bool_obj.as_object_mut()) + .map(|bool_map| { + bool_map.insert("filter".to_string(), Value::Array(filter_array)); + }); + } payload = self.build_case_insensitive_filters( payload, &case_insensitive_filters, diff --git a/crates/analytics/src/search.rs b/crates/analytics/src/search.rs index 9be0200030d..f3bda521c5b 100644 --- a/crates/analytics/src/search.rs +++ b/crates/analytics/src/search.rs @@ -5,12 +5,17 @@ use api_models::analytics::search::{ use common_utils::errors::{CustomResult, ReportSwitchExt}; use error_stack::ResultExt; use router_env::tracing; +use serde_json::Value; use crate::{ enums::AuthInfo, opensearch::{OpenSearchClient, OpenSearchError, OpenSearchQuery, OpenSearchQueryBuilder}, }; +pub fn convert_to_value>(items: Vec) -> Vec { + items.into_iter().map(|item| item.into()).collect() +} + pub async fn msearch_results( client: &OpenSearchClient, req: GetGlobalSearchRequest, @@ -38,21 +43,24 @@ pub async fn msearch_results( if let Some(currency) = filters.currency { if !currency.is_empty() { query_builder - .add_filter_clause("currency.keyword".to_string(), currency.clone()) + .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency)) .switch()?; } }; if let Some(status) = filters.status { if !status.is_empty() { query_builder - .add_filter_clause("status.keyword".to_string(), status.clone()) + .add_filter_clause("status.keyword".to_string(), convert_to_value(status)) .switch()?; } }; if let Some(payment_method) = filters.payment_method { if !payment_method.is_empty() { query_builder - .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone()) + .add_filter_clause( + "payment_method.keyword".to_string(), + convert_to_value(payment_method), + ) .switch()?; } }; @@ -61,15 +69,17 @@ pub async fn msearch_results( query_builder .add_filter_clause( "customer_email.keyword".to_string(), - customer_email - .iter() - .filter_map(|email| { - // TODO: Add trait based inputs instead of converting this to strings - serde_json::to_value(email) - .ok() - .and_then(|a| a.as_str().map(|a| a.to_string())) - }) - .collect(), + convert_to_value( + customer_email + .iter() + .filter_map(|email| { + // TODO: Add trait based inputs instead of converting this to strings + serde_json::to_value(email) + .ok() + .and_then(|a| a.as_str().map(|a| a.to_string())) + }) + .collect(), + ), ) .switch()?; } @@ -79,15 +89,17 @@ pub async fn msearch_results( query_builder .add_filter_clause( "feature_metadata.search_tags.keyword".to_string(), - search_tags - .iter() - .filter_map(|search_tag| { - // TODO: Add trait based inputs instead of converting this to strings - serde_json::to_value(search_tag) - .ok() - .and_then(|a| a.as_str().map(|a| a.to_string())) - }) - .collect(), + convert_to_value( + search_tags + .iter() + .filter_map(|search_tag| { + // TODO: Add trait based inputs instead of converting this to strings + serde_json::to_value(search_tag) + .ok() + .and_then(|a| a.as_str().map(|a| a.to_string())) + }) + .collect(), + ), ) .switch()?; } @@ -95,7 +107,7 @@ pub async fn msearch_results( if let Some(connector) = filters.connector { if !connector.is_empty() { query_builder - .add_filter_clause("connector.keyword".to_string(), connector.clone()) + .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector)) .switch()?; } }; @@ -104,7 +116,7 @@ pub async fn msearch_results( query_builder .add_filter_clause( "payment_method_type.keyword".to_string(), - payment_method_type.clone(), + convert_to_value(payment_method_type), ) .switch()?; } @@ -112,21 +124,47 @@ pub async fn msearch_results( if let Some(card_network) = filters.card_network { if !card_network.is_empty() { query_builder - .add_filter_clause("card_network.keyword".to_string(), card_network.clone()) + .add_filter_clause( + "card_network.keyword".to_string(), + convert_to_value(card_network), + ) .switch()?; } }; if let Some(card_last_4) = filters.card_last_4 { if !card_last_4.is_empty() { query_builder - .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone()) + .add_filter_clause( + "card_last_4.keyword".to_string(), + convert_to_value(card_last_4), + ) .switch()?; } }; if let Some(payment_id) = filters.payment_id { if !payment_id.is_empty() { query_builder - .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone()) + .add_filter_clause( + "payment_id.keyword".to_string(), + convert_to_value(payment_id), + ) + .switch()?; + } + }; + if let Some(amount) = filters.amount { + if !amount.is_empty() { + query_builder + .add_filter_clause("amount".to_string(), convert_to_value(amount)) + .switch()?; + } + }; + if let Some(customer_id) = filters.customer_id { + if !customer_id.is_empty() { + query_builder + .add_filter_clause( + "customer_id.keyword".to_string(), + convert_to_value(customer_id), + ) .switch()?; } }; @@ -211,21 +249,24 @@ pub async fn search_results( if let Some(currency) = filters.currency { if !currency.is_empty() { query_builder - .add_filter_clause("currency.keyword".to_string(), currency.clone()) + .add_filter_clause("currency.keyword".to_string(), convert_to_value(currency)) .switch()?; } }; if let Some(status) = filters.status { if !status.is_empty() { query_builder - .add_filter_clause("status.keyword".to_string(), status.clone()) + .add_filter_clause("status.keyword".to_string(), convert_to_value(status)) .switch()?; } }; if let Some(payment_method) = filters.payment_method { if !payment_method.is_empty() { query_builder - .add_filter_clause("payment_method.keyword".to_string(), payment_method.clone()) + .add_filter_clause( + "payment_method.keyword".to_string(), + convert_to_value(payment_method), + ) .switch()?; } }; @@ -234,15 +275,17 @@ pub async fn search_results( query_builder .add_filter_clause( "customer_email.keyword".to_string(), - customer_email - .iter() - .filter_map(|email| { - // TODO: Add trait based inputs instead of converting this to strings - serde_json::to_value(email) - .ok() - .and_then(|a| a.as_str().map(|a| a.to_string())) - }) - .collect(), + convert_to_value( + customer_email + .iter() + .filter_map(|email| { + // TODO: Add trait based inputs instead of converting this to strings + serde_json::to_value(email) + .ok() + .and_then(|a| a.as_str().map(|a| a.to_string())) + }) + .collect(), + ), ) .switch()?; } @@ -252,15 +295,17 @@ pub async fn search_results( query_builder .add_filter_clause( "feature_metadata.search_tags.keyword".to_string(), - search_tags - .iter() - .filter_map(|search_tag| { - // TODO: Add trait based inputs instead of converting this to strings - serde_json::to_value(search_tag) - .ok() - .and_then(|a| a.as_str().map(|a| a.to_string())) - }) - .collect(), + convert_to_value( + search_tags + .iter() + .filter_map(|search_tag| { + // TODO: Add trait based inputs instead of converting this to strings + serde_json::to_value(search_tag) + .ok() + .and_then(|a| a.as_str().map(|a| a.to_string())) + }) + .collect(), + ), ) .switch()?; } @@ -268,7 +313,7 @@ pub async fn search_results( if let Some(connector) = filters.connector { if !connector.is_empty() { query_builder - .add_filter_clause("connector.keyword".to_string(), connector.clone()) + .add_filter_clause("connector.keyword".to_string(), convert_to_value(connector)) .switch()?; } }; @@ -277,7 +322,7 @@ pub async fn search_results( query_builder .add_filter_clause( "payment_method_type.keyword".to_string(), - payment_method_type.clone(), + convert_to_value(payment_method_type), ) .switch()?; } @@ -285,21 +330,47 @@ pub async fn search_results( if let Some(card_network) = filters.card_network { if !card_network.is_empty() { query_builder - .add_filter_clause("card_network.keyword".to_string(), card_network.clone()) + .add_filter_clause( + "card_network.keyword".to_string(), + convert_to_value(card_network), + ) .switch()?; } }; if let Some(card_last_4) = filters.card_last_4 { if !card_last_4.is_empty() { query_builder - .add_filter_clause("card_last_4.keyword".to_string(), card_last_4.clone()) + .add_filter_clause( + "card_last_4.keyword".to_string(), + convert_to_value(card_last_4), + ) .switch()?; } }; if let Some(payment_id) = filters.payment_id { if !payment_id.is_empty() { query_builder - .add_filter_clause("payment_id.keyword".to_string(), payment_id.clone()) + .add_filter_clause( + "payment_id.keyword".to_string(), + convert_to_value(payment_id), + ) + .switch()?; + } + }; + if let Some(amount) = filters.amount { + if !amount.is_empty() { + query_builder + .add_filter_clause("amount".to_string(), convert_to_value(amount)) + .switch()?; + } + }; + if let Some(customer_id) = filters.customer_id { + if !customer_id.is_empty() { + query_builder + .add_filter_clause( + "customer_id.keyword".to_string(), + convert_to_value(customer_id), + ) .switch()?; } }; diff --git a/crates/api_models/src/analytics/search.rs b/crates/api_models/src/analytics/search.rs index a33dd100c79..06426efde6c 100644 --- a/crates/api_models/src/analytics/search.rs +++ b/crates/api_models/src/analytics/search.rs @@ -14,6 +14,8 @@ pub struct SearchFilters { pub card_network: Option>, pub card_last_4: Option>, pub payment_id: Option>, + pub amount: Option>, + pub customer_id: Option>, } impl SearchFilters { pub fn is_all_none(&self) -> bool { @@ -27,6 +29,8 @@ impl SearchFilters { && self.card_network.is_none() && self.card_last_4.is_none() && self.payment_id.is_none() + && self.amount.is_none() + && self.customer_id.is_none() } } From 45882bdb76f7f031776aa10692fabd792627b891 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Mon, 10 Feb 2025 03:03:23 +0530 Subject: [PATCH 068/133] feat(connector): [DATATRANS] Add Support for External 3DS (#7226) --- crates/common_enums/src/connector_enums.rs | 3 +- .../src/connectors/datatrans.rs | 2 +- .../src/connectors/datatrans/transformers.rs | 53 ++++++++++++++++--- 3 files changed, 47 insertions(+), 11 deletions(-) diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index a0e606e0741..2e1fd195b90 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -416,12 +416,11 @@ impl Connector { | Self::Razorpay | Self::Riskified | Self::Threedsecureio - | Self::Datatrans | Self::Netcetera | Self::CtpMastercard | Self::Noon | Self::Stripe => false, - Self::Checkout | Self::Nmi | Self::Cybersource => true, + Self::Checkout | Self::Nmi |Self::Datatrans|Self::Cybersource => true, } } diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans.rs b/crates/hyperswitch_connectors/src/connectors/datatrans.rs index b549f48984e..6c311dd4a98 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans.rs @@ -223,7 +223,7 @@ impl ConnectorIntegration CustomResult { let base_url = self.base_url(connectors); - if req.is_three_ds() { + if req.is_three_ds() && req.request.authentication_data.is_none() { Ok(format!("{base_url}v1/transactions")) } else { Ok(format!("{base_url}v1/transactions/authorize")) diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index c0f4ed22722..d2ca3d2d013 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -136,7 +136,7 @@ pub struct PlainCardDetails { pub cvv: Secret, #[serde(skip_serializing_if = "Option::is_none")] #[serde(rename = "3D")] - pub three_ds: Option, + pub three_ds: Option, } #[derive(Serialize, Clone, Debug)] @@ -144,6 +144,26 @@ pub struct ThreedsInfo { cardholder: CardHolder, } +#[derive(Serialize, Clone, Debug)] +#[serde(untagged)] +pub enum ThreeDSecureData { + Cardholder(ThreedsInfo), + Authentication(ThreeDSData), +} +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct ThreeDSData { + #[serde(rename = "threeDSTransactionId")] + pub three_ds_transaction_id: Secret, + pub cavv: Secret, + pub eci: Option, + pub xid: Secret, + #[serde(rename = "threeDSVersion")] + pub three_ds_version: String, + #[serde(rename = "authenticationResponse")] + pub authentication_response: String, +} + #[derive(Debug, Serialize, Clone, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CardHolder { @@ -240,13 +260,16 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> card: create_card_details(item, &req_card)?, refno: item.router_data.connector_request_reference_id.clone(), auto_settle: item.router_data.request.is_auto_capture()?, - redirect: match item.router_data.is_three_ds() { - true => Some(RedirectUrls { + redirect: if item.router_data.is_three_ds() + && item.router_data.request.authentication_data.is_none() + { + Some(RedirectUrls { success_url: item.router_data.request.router_return_url.clone(), cancel_url: item.router_data.request.router_return_url.clone(), error_url: item.router_data.request.router_return_url.clone(), - }), - false => None, + }) + } else { + None }, }), PaymentMethodData::Wallet(_) @@ -314,9 +337,23 @@ fn create_card_details( three_ds: None, }; - if item.router_data.is_three_ds() { + if let Some(auth_data) = &item.router_data.request.authentication_data { + details.three_ds = Some(ThreeDSecureData::Authentication(ThreeDSData { + three_ds_transaction_id: Secret::new(auth_data.threeds_server_transaction_id.clone()), + cavv: Secret::new(auth_data.cavv.clone()), + eci: auth_data.eci.clone(), + xid: Secret::new( + auth_data + .ds_trans_id + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "xid" })?, + ), + three_ds_version: auth_data.message_version.to_string(), + authentication_response: "Y".to_string(), + })); + } else if item.router_data.is_three_ds() { let billing = item.router_data.get_billing_address()?; - details.three_ds = Some(ThreedsInfo { + details.three_ds = Some(ThreeDSecureData::Cardholder(ThreedsInfo { cardholder: CardHolder { cardholder_name: item.router_data.get_billing_full_name()?, email: item.router_data.request.get_email()?, @@ -326,7 +363,7 @@ fn create_card_details( bill_addr_state: billing.get_state().ok().cloned(), bill_addr_country: billing.get_country().ok().copied(), }, - }); + })); } Ok(details) } From 7ea63b44ee057b918705ab41367e3a4ab1c192e5 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Feb 2025 00:31:51 +0000 Subject: [PATCH 069/133] chore(version): 2025.02.10.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 865d2b7e020..6f8bc2313ce 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.10.0 + +### Features + +- **connector:** + - [DataTrans] ADD 3DS Flow ([#6026](https://github.com/juspay/hyperswitch/pull/6026)) ([`4693d21`](https://github.com/juspay/hyperswitch/commit/4693d21b7c26055ed33fadd3f53943715ab71516)) + - [DATATRANS] Add Support for External 3DS ([#7226](https://github.com/juspay/hyperswitch/pull/7226)) ([`45882bd`](https://github.com/juspay/hyperswitch/commit/45882bdb76f7f031776aa10692fabd792627b891)) +- **opensearch:** Add amount and customer_id as filters and handle name for different indexes ([#7073](https://github.com/juspay/hyperswitch/pull/7073)) ([`df328c5`](https://github.com/juspay/hyperswitch/commit/df328c5e520b89b09e1b684d039f1d9613d78613)) + +### Refactors + +- **connector:** Move connectors Aci, Braintree, Globalpay, Iatapay, Itaubank, Klarna, Mifinity and Nuvei from router to hyperswitch_connectors crate ([#7167](https://github.com/juspay/hyperswitch/pull/7167)) ([`7dfe400`](https://github.com/juspay/hyperswitch/commit/7dfe400401daf7081f9240ed52064281b302ba57)) +- **router:** Add display_name field to connector feature api ([#7121](https://github.com/juspay/hyperswitch/pull/7121)) ([`50784ad`](https://github.com/juspay/hyperswitch/commit/50784ad1c13f0aa66a1da566ddd25e2621021538)) + +**Full Changelog:** [`2025.02.07.0...2025.02.10.0`](https://github.com/juspay/hyperswitch/compare/2025.02.07.0...2025.02.10.0) + +- - - + ## 2025.02.07.0 ### Features From 1c54211b2f8aa650fc4dbb7ab3d796e21d50461a Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 10 Feb 2025 11:22:54 +0530 Subject: [PATCH 070/133] fix(connector): [fiuu] update PSync and webhooks response (#7211) --- .../src/connectors/fiuu.rs | 4 +++- .../src/connectors/fiuu/transformers.rs | 17 +++++++++++------ 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 4dc87c244ac..7e9bb8c120c 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -866,7 +866,9 @@ impl webhooks::IncomingWebhook for Fiuu { webhooks_payment_response.paydate, webhooks_payment_response.domain.peek(), md5_key0, - webhooks_payment_response.appcode.peek(), + webhooks_payment_response + .appcode + .map_or("".to_string(), |appcode| appcode.expose()), String::from_utf8_lossy(&connector_webhook_secrets.secret) ); key1 diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index e23cdd548cb..b5d4408d6bf 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -1145,8 +1145,8 @@ pub struct FiuuPaymentSyncResponse { stat_name: StatName, #[serde(rename = "TranID")] tran_id: String, - error_code: String, - error_desc: String, + error_code: Option, + error_desc: Option, #[serde(rename = "miscellaneous")] miscellaneous: Option>>, #[serde(rename = "SchemeTransactionID")] @@ -1228,9 +1228,14 @@ impl TryFrom> for PaymentsSy let error_response = if status == enums::AttemptStatus::Failure { Some(ErrorResponse { status_code: item.http_code, - code: response.error_code.clone(), - message: response.error_desc.clone(), - reason: Some(response.error_desc), + code: response + .error_code + .unwrap_or(consts::NO_ERROR_CODE.to_owned()), + message: response + .error_desc + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_owned()), + reason: response.error_desc, attempt_status: Some(enums::AttemptStatus::Failure), connector_transaction_id: Some(txn_id.clone()), }) @@ -1759,7 +1764,7 @@ pub struct FiuuWebhooksPaymentResponse { pub amount: StringMajorUnit, pub currency: String, pub domain: Secret, - pub appcode: Secret, + pub appcode: Option>, pub paydate: String, pub channel: String, pub error_desc: Option, From 323d763087fd7453f05153b97d6b53e211cf74ba Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Mon, 10 Feb 2025 11:56:39 +0530 Subject: [PATCH 071/133] feat(router): add adyen split payments support (#6952) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 168 ++++++++++--- api-reference/openapi_spec.json | 172 ++++++++++--- connector-template/transformers.rs | 2 +- crates/api_models/src/payments.rs | 28 +-- crates/common_enums/src/enums.rs | 38 +++ crates/common_types/src/domain.rs | 43 ++++ crates/common_types/src/lib.rs | 1 + crates/common_types/src/payments.rs | 83 +++++-- crates/common_types/src/refunds.rs | 4 + crates/diesel_models/src/payment_attempt.rs | 62 ++--- crates/diesel_models/src/schema.rs | 1 + crates/diesel_models/src/schema_v2.rs | 3 +- crates/diesel_models/src/user/sample_data.rs | 1 - .../src/connectors/aci/transformers.rs | 2 +- .../src/connectors/airwallex/transformers.rs | 4 +- .../src/connectors/amazonpay/transformers.rs | 2 +- .../src/connectors/bambora/transformers.rs | 12 +- .../connectors/bamboraapac/transformers.rs | 8 +- .../connectors/bankofamerica/transformers.rs | 8 +- .../src/connectors/billwerk/transformers.rs | 2 +- .../src/connectors/bitpay/transformers.rs | 2 +- .../src/connectors/bluesnap.rs | 2 +- .../src/connectors/bluesnap/transformers.rs | 2 +- .../src/connectors/boku/transformers.rs | 2 +- .../src/connectors/braintree/transformers.rs | 18 +- .../src/connectors/cashtocode/transformers.rs | 4 +- .../src/connectors/chargebee/transformers.rs | 2 +- .../src/connectors/coinbase/transformers.rs | 2 +- .../src/connectors/coingate/transformers.rs | 2 +- .../src/connectors/cryptopay/transformers.rs | 2 +- .../connectors/cybersource/transformers.rs | 12 +- .../src/connectors/datatrans/transformers.rs | 6 +- .../connectors/deutschebank/transformers.rs | 12 +- .../connectors/digitalvirgo/transformers.rs | 4 +- .../src/connectors/dlocal/transformers.rs | 8 +- .../src/connectors/elavon/transformers.rs | 6 +- .../src/connectors/fiserv/transformers.rs | 4 +- .../src/connectors/fiservemea/transformers.rs | 2 +- .../src/connectors/fiuu/transformers.rs | 18 +- .../src/connectors/forte/transformers.rs | 8 +- .../src/connectors/globalpay/transformers.rs | 2 +- .../src/connectors/globepay/transformers.rs | 4 +- .../src/connectors/gocardless/transformers.rs | 6 +- .../src/connectors/helcim/transformers.rs | 10 +- .../src/connectors/iatapay/transformers.rs | 4 +- .../src/connectors/inespay/transformers.rs | 6 +- .../src/connectors/itaubank/transformers.rs | 4 +- .../src/connectors/jpmorgan/transformers.rs | 8 +- .../src/connectors/klarna/transformers.rs | 10 +- .../src/connectors/mifinity/transformers.rs | 10 +- .../src/connectors/mollie/transformers.rs | 2 +- .../connectors/multisafepay/transformers.rs | 2 +- .../src/connectors/nexinets/transformers.rs | 4 +- .../src/connectors/nexixpay/transformers.rs | 14 +- .../src/connectors/nomupay/transformers.rs | 2 +- .../src/connectors/novalnet/transformers.rs | 8 +- .../src/connectors/nuvei/transformers.rs | 2 +- .../src/connectors/paybox/transformers.rs | 10 +- .../src/connectors/payeezy/transformers.rs | 2 +- .../src/connectors/payu/transformers.rs | 8 +- .../src/connectors/placetopay/transformers.rs | 2 +- .../src/connectors/powertranz/transformers.rs | 2 +- .../src/connectors/prophetpay/transformers.rs | 8 +- .../src/connectors/rapyd/transformers.rs | 2 +- .../src/connectors/razorpay/transformers.rs | 4 +- .../src/connectors/redsys/transformers.rs | 2 +- .../src/connectors/shift4/transformers.rs | 4 +- .../src/connectors/square/transformers.rs | 2 +- .../src/connectors/stax/transformers.rs | 2 +- .../src/connectors/thunes/transformers.rs | 2 +- .../src/connectors/tsys/transformers.rs | 4 +- .../src/connectors/volt/transformers.rs | 6 +- .../src/connectors/wellsfargo/transformers.rs | 8 +- .../src/connectors/worldline/transformers.rs | 4 +- .../src/connectors/worldpay.rs | 6 +- .../src/connectors/worldpay/transformers.rs | 2 +- .../src/connectors/xendit/transformers.rs | 6 +- .../src/connectors/zen/transformers.rs | 4 +- .../src/connectors/zsl/transformers.rs | 4 +- .../src/payments/payment_attempt.rs | 30 +-- .../src/router_data.rs | 8 +- .../src/router_request_types.rs | 1 + .../src/router_response_types.rs | 12 +- crates/openapi/src/openapi.rs | 7 +- crates/openapi/src/openapi_v2.rs | 7 +- .../src/connector/adyen/transformers.rs | 225 +++++++++++++++++- .../connector/authorizedotnet/transformers.rs | 8 +- .../src/connector/checkout/transformers.rs | 8 +- .../connector/dummyconnector/transformers.rs | 2 +- .../router/src/connector/nmi/transformers.rs | 14 +- .../router/src/connector/noon/transformers.rs | 2 +- .../src/connector/opayo/transformers.rs | 2 +- .../src/connector/opennode/transformers.rs | 2 +- .../src/connector/payme/transformers.rs | 8 +- crates/router/src/connector/paypal.rs | 4 +- .../src/connector/paypal/transformers.rs | 20 +- .../src/connector/plaid/transformers.rs | 4 +- crates/router/src/connector/stripe.rs | 122 +++++----- .../src/connector/stripe/transformers.rs | 65 ++++- .../src/connector/trustpay/transformers.rs | 10 +- crates/router/src/connector/utils.rs | 34 +++ .../wellsfargopayout/transformers.rs | 2 +- crates/router/src/core/payments/helpers.rs | 95 +++++++- .../payments/operations/payment_create.rs | 3 +- .../payments/operations/payment_response.rs | 4 +- crates/router/src/core/payments/retry.rs | 5 +- .../router/src/core/payments/transformers.rs | 22 +- crates/router/src/core/refunds.rs | 89 ++----- .../router/src/core/refunds/transformers.rs | 54 +---- crates/router/src/core/refunds/validator.rs | 124 ++++++++-- crates/router/src/core/utils.rs | 80 +++++++ .../src/types/storage/payment_attempt.rs | 3 - crates/router/tests/connectors/utils.rs | 2 +- .../src/mock_db/payment_attempt.rs | 3 +- .../src/payments/payment_attempt.rs | 7 +- .../down.sql | 2 + .../up.sql | 3 + .../down.sql | 3 +- .../2024-11-08-081847_drop_v1_columns/up.sql | 3 +- 119 files changed, 1387 insertions(+), 666 deletions(-) create mode 100644 crates/common_types/src/domain.rs create mode 100644 migrations/2025-01-14-832737_add_charges_to_payment_attempt/down.sql create mode 100644 migrations/2025-01-14-832737_add_charges_to_payment_attempt/up.sql diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 329bab6f3dd..7cdd6fe9d31 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -3076,6 +3076,79 @@ }, "additionalProperties": false }, + "AdyenSplitData": { + "type": "object", + "description": "Fee information for Split Payments to be charged on the payment being collected for Adyen", + "required": [ + "split_items" + ], + "properties": { + "store": { + "type": "string", + "description": "The store identifier", + "nullable": true + }, + "split_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdyenSplitItem" + }, + "description": "Data for the split items" + } + }, + "additionalProperties": false + }, + "AdyenSplitItem": { + "type": "object", + "description": "Data for the split items", + "required": [ + "amount", + "split_type", + "reference" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The amount of the split item", + "example": 6540 + }, + "split_type": { + "$ref": "#/components/schemas/AdyenSplitType" + }, + "account": { + "type": "string", + "description": "The unique identifier of the account to which the split amount is allocated.", + "nullable": true + }, + "reference": { + "type": "string", + "description": "Unique Identifier for the split item" + }, + "description": { + "type": "string", + "description": "Description for the part of the payment that will be allocated to the specified account.", + "nullable": true + } + }, + "additionalProperties": false + }, + "AdyenSplitType": { + "type": "string", + "enum": [ + "BalanceAccount", + "AcquiringFees", + "PaymentFee", + "AdyenFees", + "AdyenCommission", + "AdyenMarkup", + "Interchange", + "SchemeFee", + "Commission", + "TopUp", + "Vat" + ] + }, "AirwallexData": { "type": "object", "properties": { @@ -6766,6 +6839,33 @@ "zsl" ] }, + "ConnectorChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeChargeResponseData" + } + } + }, + { + "type": "object", + "required": [ + "adyen_split_payment" + ], + "properties": { + "adyen_split_payment": { + "$ref": "#/components/schemas/AdyenSplitData" + } + } + } + ], + "description": "Charge Information" + }, "ConnectorFeatureMatrixResponse": { "type": "object", "required": [ @@ -19763,24 +19863,20 @@ "$ref": "#/components/schemas/StripeSplitPaymentRequest" } } - } - ], - "description": "Fee information for Split Payments to be charged on the payment being collected" - }, - "SplitPaymentsResponse": { - "oneOf": [ + }, { "type": "object", "required": [ - "stripe_split_payment" + "adyen_split_payment" ], "properties": { - "stripe_split_payment": { - "$ref": "#/components/schemas/StripeSplitPaymentsResponse" + "adyen_split_payment": { + "$ref": "#/components/schemas/AdyenSplitData" } } } - ] + ], + "description": "Fee information for Split Payments to be charged on the payment being collected" }, "SplitRefund": { "oneOf": [ @@ -19794,6 +19890,17 @@ "$ref": "#/components/schemas/StripeSplitRefundRequest" } } + }, + { + "type": "object", + "required": [ + "adyen_split_refund" + ], + "properties": { + "adyen_split_refund": { + "$ref": "#/components/schemas/AdyenSplitData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -19868,66 +19975,67 @@ "propertyName": "type" } }, - "StripeChargeType": { - "type": "string", - "enum": [ - "direct", - "destination" - ] - }, - "StripeSplitPaymentRequest": { + "StripeChargeResponseData": { "type": "object", - "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", + "description": "Fee information to be charged on the payment being collected via Stripe", "required": [ "charge_type", "application_fees", "transfer_account_id" ], "properties": { + "charge_id": { + "type": "string", + "description": "Identifier for charge created for the payment", + "nullable": true + }, "charge_type": { "$ref": "#/components/schemas/PaymentChargeType" }, "application_fees": { "type": "integer", "format": "int64", - "description": "Platform fees to be collected on the payment", + "description": "Platform fees collected on the payment", "example": 6540 }, "transfer_account_id": { "type": "string", - "description": "Identifier for the reseller's account to send the funds to" + "description": "Identifier for the reseller's account where the funds were transferred" } }, "additionalProperties": false }, - "StripeSplitPaymentsResponse": { + "StripeChargeType": { + "type": "string", + "enum": [ + "direct", + "destination" + ] + }, + "StripeSplitPaymentRequest": { "type": "object", - "description": "Fee information to be charged on the payment being collected", + "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", "required": [ "charge_type", "application_fees", "transfer_account_id" ], "properties": { - "charge_id": { - "type": "string", - "description": "Identifier for charge created for the payment", - "nullable": true - }, "charge_type": { "$ref": "#/components/schemas/PaymentChargeType" }, "application_fees": { "type": "integer", "format": "int64", - "description": "Platform fees collected on the payment", + "description": "Platform fees to be collected on the payment", "example": 6540 }, "transfer_account_id": { "type": "string", "description": "Identifier for the reseller's account where the funds were transferred" } - } + }, + "additionalProperties": false }, "StripeSplitRefundRequest": { "type": "object", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index d1bfc5dfdba..60be7f59d67 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -6008,6 +6008,79 @@ }, "additionalProperties": false }, + "AdyenSplitData": { + "type": "object", + "description": "Fee information for Split Payments to be charged on the payment being collected for Adyen", + "required": [ + "split_items" + ], + "properties": { + "store": { + "type": "string", + "description": "The store identifier", + "nullable": true + }, + "split_items": { + "type": "array", + "items": { + "$ref": "#/components/schemas/AdyenSplitItem" + }, + "description": "Data for the split items" + } + }, + "additionalProperties": false + }, + "AdyenSplitItem": { + "type": "object", + "description": "Data for the split items", + "required": [ + "amount", + "split_type", + "reference" + ], + "properties": { + "amount": { + "type": "integer", + "format": "int64", + "description": "The amount of the split item", + "example": 6540 + }, + "split_type": { + "$ref": "#/components/schemas/AdyenSplitType" + }, + "account": { + "type": "string", + "description": "The unique identifier of the account to which the split amount is allocated.", + "nullable": true + }, + "reference": { + "type": "string", + "description": "Unique Identifier for the split item" + }, + "description": { + "type": "string", + "description": "Description for the part of the payment that will be allocated to the specified account.", + "nullable": true + } + }, + "additionalProperties": false + }, + "AdyenSplitType": { + "type": "string", + "enum": [ + "BalanceAccount", + "AcquiringFees", + "PaymentFee", + "AdyenFees", + "AdyenCommission", + "AdyenMarkup", + "Interchange", + "SchemeFee", + "Commission", + "TopUp", + "Vat" + ] + }, "AirwallexData": { "type": "object", "properties": { @@ -9516,6 +9589,33 @@ "zsl" ] }, + "ConnectorChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "stripe_split_payment" + ], + "properties": { + "stripe_split_payment": { + "$ref": "#/components/schemas/StripeChargeResponseData" + } + } + }, + { + "type": "object", + "required": [ + "adyen_split_payment" + ], + "properties": { + "adyen_split_payment": { + "$ref": "#/components/schemas/AdyenSplitData" + } + } + } + ], + "description": "Charge Information" + }, "ConnectorFeatureMatrixResponse": { "type": "object", "required": [ @@ -19083,7 +19183,7 @@ "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/SplitPaymentsResponse" + "$ref": "#/components/schemas/ConnectorChargeResponseData" } ], "nullable": true @@ -20326,7 +20426,7 @@ "split_payments": { "allOf": [ { - "$ref": "#/components/schemas/SplitPaymentsResponse" + "$ref": "#/components/schemas/ConnectorChargeResponseData" } ], "nullable": true @@ -25258,24 +25358,20 @@ "$ref": "#/components/schemas/StripeSplitPaymentRequest" } } - } - ], - "description": "Fee information for Split Payments to be charged on the payment being collected" - }, - "SplitPaymentsResponse": { - "oneOf": [ + }, { "type": "object", "required": [ - "stripe_split_payment" + "adyen_split_payment" ], "properties": { - "stripe_split_payment": { - "$ref": "#/components/schemas/StripeSplitPaymentsResponse" + "adyen_split_payment": { + "$ref": "#/components/schemas/AdyenSplitData" } } } - ] + ], + "description": "Fee information for Split Payments to be charged on the payment being collected" }, "SplitRefund": { "oneOf": [ @@ -25289,6 +25385,17 @@ "$ref": "#/components/schemas/StripeSplitRefundRequest" } } + }, + { + "type": "object", + "required": [ + "adyen_split_refund" + ], + "properties": { + "adyen_split_refund": { + "$ref": "#/components/schemas/AdyenSplitData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -25363,66 +25470,67 @@ "propertyName": "type" } }, - "StripeChargeType": { - "type": "string", - "enum": [ - "direct", - "destination" - ] - }, - "StripeSplitPaymentRequest": { + "StripeChargeResponseData": { "type": "object", - "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", + "description": "Fee information to be charged on the payment being collected via Stripe", "required": [ "charge_type", "application_fees", "transfer_account_id" ], "properties": { + "charge_id": { + "type": "string", + "description": "Identifier for charge created for the payment", + "nullable": true + }, "charge_type": { "$ref": "#/components/schemas/PaymentChargeType" }, "application_fees": { "type": "integer", "format": "int64", - "description": "Platform fees to be collected on the payment", + "description": "Platform fees collected on the payment", "example": 6540 }, "transfer_account_id": { "type": "string", - "description": "Identifier for the reseller's account to send the funds to" + "description": "Identifier for the reseller's account where the funds were transferred" } }, "additionalProperties": false }, - "StripeSplitPaymentsResponse": { + "StripeChargeType": { + "type": "string", + "enum": [ + "direct", + "destination" + ] + }, + "StripeSplitPaymentRequest": { "type": "object", - "description": "Fee information to be charged on the payment being collected", + "description": "Fee information for Split Payments to be charged on the payment being collected for Stripe", "required": [ "charge_type", "application_fees", "transfer_account_id" ], "properties": { - "charge_id": { - "type": "string", - "description": "Identifier for charge created for the payment", - "nullable": true - }, "charge_type": { "$ref": "#/components/schemas/PaymentChargeType" }, "application_fees": { "type": "integer", "format": "int64", - "description": "Platform fees collected on the payment", + "description": "Platform fees to be collected on the payment", "example": 6540 }, "transfer_account_id": { "type": "string", "description": "Identifier for the reseller's account where the funds were transferred" } - } + }, + "additionalProperties": false }, "StripeSplitRefundRequest": { "type": "object", diff --git a/connector-template/transformers.rs b/connector-template/transformers.rs index b508596cbc0..73e167d89fd 100644 --- a/connector-template/transformers.rs +++ b/connector-template/transformers.rs @@ -138,7 +138,7 @@ impl TryFrom, /// Fee information to be charged on the payment being collected - pub split_payments: Option, + #[schema(value_type = Option)] + pub split_payments: Option, /// You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM. #[schema(value_type = Option, example = r#"{ "fulfillment_method" : "deliver", "coverage_request" : "fraud" }"#)] @@ -5276,31 +5277,6 @@ pub struct PaymentStartRedirectionParams { pub profile_id: id_type::ProfileId, } -/// Fee information to be charged on the payment being collected -#[derive(Setter, Clone, Debug, PartialEq, serde::Serialize, ToSchema)] -pub struct StripeSplitPaymentsResponse { - /// Identifier for charge created for the payment - pub charge_id: Option, - - /// Type of charge (connector specific) - #[schema(value_type = PaymentChargeType, example = "direct")] - pub charge_type: api_enums::PaymentChargeType, - - /// Platform fees collected on the payment - #[schema(value_type = i64, example = 6540)] - pub application_fees: MinorUnit, - - /// Identifier for the reseller's account where the funds were transferred - pub transfer_account_id: String, -} - -#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] -#[serde(rename_all = "snake_case")] -pub enum SplitPaymentsResponse { - /// StripeSplitPaymentsResponse - StripeSplitPayment(StripeSplitPaymentsResponse), -} - /// Details of external authentication #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct ExternalAuthenticationDetailsResponse { diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 1faa3b90aa8..c4ee48ed5ac 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3696,3 +3696,41 @@ pub enum GooglePayAuthMethod { #[serde(rename = "CRYPTOGRAM_3DS")] Cryptogram, } + +#[derive( + Clone, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[strum(serialize_all = "PascalCase")] +#[serde(rename_all = "PascalCase")] +pub enum AdyenSplitType { + /// Books split amount to the specified account. + BalanceAccount, + /// The aggregated amount of the interchange and scheme fees. + AcquiringFees, + /// The aggregated amount of all transaction fees. + PaymentFee, + /// The aggregated amount of Adyen's commission and markup fees. + AdyenFees, + /// The transaction fees due to Adyen under blended rates. + AdyenCommission, + /// The transaction fees due to Adyen under Interchange ++ pricing. + AdyenMarkup, + /// The fees paid to the issuer for each payment made with the card network. + Interchange, + /// The fees paid to the card scheme for using their network. + SchemeFee, + /// Your platform's commission on the payment (specified in amount), booked to your liable balance account. + Commission, + /// Allows you and your users to top up balance accounts using direct debit, card payments, or other payment methods. + TopUp, + /// The value-added tax charged on the payment, booked to your platforms liable balance account. + Vat, +} diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs new file mode 100644 index 00000000000..65f6ae9ad68 --- /dev/null +++ b/crates/common_types/src/domain.rs @@ -0,0 +1,43 @@ +//! Common types + +use common_enums::enums; +use common_utils::{impl_to_sql_from_sql_json, types::MinorUnit}; +use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; +use serde::{Deserialize, Serialize}; +use utoipa::ToSchema; + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Fee information for Split Payments to be charged on the payment being collected for Adyen +pub struct AdyenSplitData { + /// The store identifier + pub store: Option, + /// Data for the split items + pub split_items: Vec, +} +impl_to_sql_from_sql_json!(AdyenSplitData); + +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Data for the split items +pub struct AdyenSplitItem { + /// The amount of the split item + #[schema(value_type = i64, example = 6540)] + pub amount: Option, + /// Defines type of split item + #[schema(value_type = AdyenSplitType, example = "BalanceAccount")] + pub split_type: enums::AdyenSplitType, + /// The unique identifier of the account to which the split amount is allocated. + pub account: Option, + /// Unique Identifier for the split item + pub reference: String, + /// Description for the part of the payment that will be allocated to the specified account. + pub description: Option, +} +impl_to_sql_from_sql_json!(AdyenSplitItem); diff --git a/crates/common_types/src/lib.rs b/crates/common_types/src/lib.rs index b0b258ecb6d..6139ddff16a 100644 --- a/crates/common_types/src/lib.rs +++ b/crates/common_types/src/lib.rs @@ -2,6 +2,7 @@ #![warn(missing_docs, missing_debug_implementations)] +pub mod domain; pub mod payment_methods; pub mod payments; pub mod refunds; diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index ea42fbf7a41..ba96d618c41 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -12,6 +12,8 @@ use euclid::frontend::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; +use crate::domain::AdyenSplitData; + #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -22,9 +24,31 @@ use utoipa::ToSchema; pub enum SplitPaymentsRequest { /// StripeSplitPayment StripeSplitPayment(StripeSplitPaymentRequest), + /// AdyenSplitPayment + AdyenSplitPayment(AdyenSplitData), } impl_to_sql_from_sql_json!(SplitPaymentsRequest); +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +/// Fee information for Split Payments to be charged on the payment being collected for Stripe +pub struct StripeSplitPaymentRequest { + /// Stripe's charge type + #[schema(value_type = PaymentChargeType, example = "direct")] + pub charge_type: enums::PaymentChargeType, + + /// Platform fees to be collected on the payment + #[schema(value_type = i64, example = 6540)] + pub application_fees: MinorUnit, + + /// Identifier for the reseller's account where the funds were transferred + pub transfer_account_id: String, +} +impl_to_sql_from_sql_json!(StripeSplitPaymentRequest); + #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -50,26 +74,6 @@ impl AuthenticationConnectorAccountMap { } } -#[derive( - Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, -)] -#[diesel(sql_type = Jsonb)] -#[serde(deny_unknown_fields)] -/// Fee information for Split Payments to be charged on the payment being collected for Stripe -pub struct StripeSplitPaymentRequest { - /// Stripe's charge type - #[schema(value_type = PaymentChargeType, example = "direct")] - pub charge_type: enums::PaymentChargeType, - - /// Platform fees to be collected on the payment - #[schema(value_type = i64, example = 6540)] - pub application_fees: MinorUnit, - - /// Identifier for the reseller's account to send the funds to - pub transfer_account_id: String, -} -impl_to_sql_from_sql_json!(StripeSplitPaymentRequest); - #[derive( Serialize, Default, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -116,3 +120,42 @@ impl_to_sql_from_sql_json!(DecisionManagerRecord); /// DecisionManagerResponse pub type DecisionManagerResponse = DecisionManagerRecord; + +/// Fee information to be charged on the payment being collected via Stripe +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct StripeChargeResponseData { + /// Identifier for charge created for the payment + pub charge_id: Option, + + /// Type of charge (connector specific) + #[schema(value_type = PaymentChargeType, example = "direct")] + pub charge_type: enums::PaymentChargeType, + + /// Platform fees collected on the payment + #[schema(value_type = i64, example = 6540)] + pub application_fees: MinorUnit, + + /// Identifier for the reseller's account where the funds were transferred + pub transfer_account_id: String, +} +impl_to_sql_from_sql_json!(StripeChargeResponseData); + +/// Charge Information +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum ConnectorChargeResponseData { + /// StripeChargeResponseData + StripeSplitPayment(StripeChargeResponseData), + /// AdyenChargeResponseData + AdyenSplitPayment(AdyenSplitData), +} + +impl_to_sql_from_sql_json!(ConnectorChargeResponseData); diff --git a/crates/common_types/src/refunds.rs b/crates/common_types/src/refunds.rs index e4b7c5a336f..c96937021c8 100644 --- a/crates/common_types/src/refunds.rs +++ b/crates/common_types/src/refunds.rs @@ -5,6 +5,8 @@ use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; +use crate::domain::AdyenSplitData; + #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, )] @@ -15,6 +17,8 @@ use utoipa::ToSchema; pub enum SplitRefund { /// StripeSplitRefundRequest StripeSplitRefund(StripeSplitRefundRequest), + /// AdyenSplitRefundRequest + AdyenSplitRefund(AdyenSplitData), } impl_to_sql_from_sql_json!(SplitRefund); diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index a8fdf882e70..eab95f81316 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -73,7 +73,6 @@ pub struct PaymentAttempt { pub authentication_connector: Option, pub authentication_id: Option, pub fingerprint_id: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, pub customer_acceptance: Option, @@ -95,6 +94,7 @@ pub struct PaymentAttempt { pub shipping_cost: Option, pub order_tax_amount: Option, pub card_discovery: Option, + pub charges: Option, } #[cfg(feature = "v1")] @@ -174,6 +174,7 @@ pub struct PaymentAttempt { pub connector_transaction_data: Option, pub connector_mandate_detail: Option, pub card_discovery: Option, + pub charges: Option, } #[cfg(feature = "v1")] @@ -287,7 +288,6 @@ pub struct PaymentAttemptNew { pub authentication_id: Option, pub fingerprint_id: Option, pub payment_method_billing_address: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, pub customer_acceptance: Option, @@ -301,6 +301,7 @@ pub struct PaymentAttemptNew { pub id: id_type::GlobalAttemptId, pub connector_token_details: Option, pub card_discovery: Option, + pub charges: Option, } #[cfg(feature = "v1")] @@ -364,7 +365,6 @@ pub struct PaymentAttemptNew { pub mandate_data: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, pub customer_acceptance: Option, @@ -494,8 +494,8 @@ pub enum PaymentAttemptUpdate { unified_code: Option>, unified_message: Option>, payment_method_data: Option, - charge_id: Option, connector_mandate_detail: Option, + charges: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -550,7 +550,7 @@ pub enum PaymentAttemptUpdate { encoded_data: Option, connector_transaction_id: Option, connector: Option, - charge_id: Option, + charges: Option, updated_by: String, }, IncrementalAuthorizationAmountUpdate { @@ -865,7 +865,6 @@ pub struct PaymentAttemptUpdateInternal { pub authentication_id: Option, pub fingerprint_id: Option, pub payment_method_billing_address_id: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, pub customer_acceptance: Option, @@ -875,6 +874,7 @@ pub struct PaymentAttemptUpdateInternal { pub connector_transaction_data: Option, pub connector_mandate_detail: Option, pub card_discovery: Option, + pub charges: Option, } #[cfg(feature = "v1")] @@ -1049,7 +1049,6 @@ impl PaymentAttemptUpdate { authentication_id, payment_method_billing_address_id, fingerprint_id, - charge_id, client_source, client_version, customer_acceptance, @@ -1059,6 +1058,7 @@ impl PaymentAttemptUpdate { connector_transaction_data, connector_mandate_detail, card_discovery, + charges, } = PaymentAttemptUpdateInternal::from(self).populate_derived_fields(&source); PaymentAttempt { amount: amount.unwrap_or(source.amount), @@ -1107,7 +1107,6 @@ impl PaymentAttemptUpdate { payment_method_billing_address_id: payment_method_billing_address_id .or(source.payment_method_billing_address_id), fingerprint_id: fingerprint_id.or(source.fingerprint_id), - charge_id: charge_id.or(source.charge_id), client_source: client_source.or(source.client_source), client_version: client_version.or(source.client_version), customer_acceptance: customer_acceptance.or(source.customer_acceptance), @@ -1118,6 +1117,7 @@ impl PaymentAttemptUpdate { .or(source.connector_transaction_data), connector_mandate_detail: connector_mandate_detail.or(source.connector_mandate_detail), card_discovery: card_discovery.or(source.card_discovery), + charges: charges.or(source.charges), ..source } } @@ -2161,7 +2161,6 @@ impl From for PaymentAttemptUpdateInternal { external_three_ds_authentication_attempted: None, authentication_connector: None, authentication_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2171,6 +2170,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::AuthenticationTypeUpdate { authentication_type, @@ -2218,7 +2218,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2228,6 +2227,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::ConfirmUpdate { amount, @@ -2310,13 +2310,13 @@ impl From for PaymentAttemptUpdateInternal { encoded_data: None, unified_code: None, unified_message: None, - charge_id: None, card_network: None, shipping_cost, order_tax_amount, connector_transaction_data: None, connector_mandate_detail, card_discovery, + charges: None, }, PaymentAttemptUpdate::VoidUpdate { status, @@ -2365,7 +2365,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2375,6 +2374,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::RejectUpdate { status, @@ -2424,7 +2424,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2434,6 +2433,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::BlocklistUpdate { status, @@ -2483,7 +2483,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2493,6 +2492,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::ConnectorMandateDetailUpdate { connector_mandate_detail, @@ -2540,7 +2540,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2550,6 +2549,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::PaymentMethodDetailsUpdate { payment_method_id, @@ -2597,7 +2597,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2607,6 +2606,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::ResponseUpdate { status, @@ -2628,8 +2628,8 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, payment_method_data, - charge_id, connector_mandate_detail, + charges, } => { let (connector_transaction_id, connector_transaction_data) = connector_transaction_id @@ -2657,7 +2657,6 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, payment_method_data, - charge_id, connector_transaction_data, amount: None, net_amount: None, @@ -2689,6 +2688,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail, card_discovery: None, + charges, } } PaymentAttemptUpdate::ErrorUpdate { @@ -2754,7 +2754,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2763,6 +2762,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, + charges: None, } } PaymentAttemptUpdate::StatusUpdate { status, updated_by } => Self { @@ -2808,7 +2808,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2818,6 +2817,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::UpdateTrackers { payment_token, @@ -2871,7 +2871,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2881,6 +2880,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::UnresolvedResponseUpdate { status, @@ -2942,7 +2942,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -2951,6 +2950,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, + charges: None, } } PaymentAttemptUpdate::PreprocessingUpdate { @@ -3011,7 +3011,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3020,6 +3019,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, + charges: None, } } PaymentAttemptUpdate::CaptureUpdate { @@ -3069,7 +3069,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3079,6 +3078,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::AmountToCaptureUpdate { status, @@ -3127,7 +3127,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3137,6 +3136,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::ConnectorResponse { authentication_data, @@ -3144,7 +3144,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector, updated_by, - charge_id, + charges, } => { let (connector_transaction_id, connector_transaction_data) = connector_transaction_id @@ -3158,7 +3158,6 @@ impl From for PaymentAttemptUpdateInternal { connector: connector.map(Some), modified_at: common_utils::date_time::now(), updated_by, - charge_id, connector_transaction_data, amount: None, net_amount: None, @@ -3204,6 +3203,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, + charges, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3252,7 +3252,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3262,6 +3261,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::AuthenticationUpdate { status, @@ -3312,7 +3312,6 @@ impl From for PaymentAttemptUpdateInternal { unified_message: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3322,6 +3321,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, PaymentAttemptUpdate::ManualUpdate { status, @@ -3382,7 +3382,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3391,6 +3390,7 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, + charges: None, } } PaymentAttemptUpdate::PostSessionTokensUpdate { @@ -3439,7 +3439,6 @@ impl From for PaymentAttemptUpdateInternal { authentication_id: None, fingerprint_id: None, payment_method_billing_address_id: None, - charge_id: None, client_source: None, client_version: None, customer_acceptance: None, @@ -3449,6 +3448,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_data: None, connector_mandate_detail: None, card_discovery: None, + charges: None, }, } } diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 170eb576e34..bbde92ea040 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -910,6 +910,7 @@ diesel::table! { connector_transaction_data -> Nullable, connector_mandate_detail -> Nullable, card_discovery -> Nullable, + charges -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 19456a5c65b..59a265e25f5 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -849,8 +849,6 @@ diesel::table! { #[max_length = 64] fingerprint_id -> Nullable, #[max_length = 64] - charge_id -> Nullable, - #[max_length = 64] client_source -> Nullable, #[max_length = 64] client_version -> Nullable, @@ -881,6 +879,7 @@ diesel::table! { shipping_cost -> Nullable, order_tax_amount -> Nullable, card_discovery -> Nullable, + charges -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 4262276c467..c063545d81f 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -274,7 +274,6 @@ impl PaymentAttemptBatchNew { mandate_data: self.mandate_data, payment_method_billing_address_id: self.payment_method_billing_address_id, fingerprint_id: self.fingerprint_id, - charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, customer_acceptance: self.customer_acceptance, diff --git a/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs index 3246f7d01d5..9fe45cef21e 100644 --- a/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/aci/transformers.rs @@ -745,7 +745,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -593,7 +593,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -628,7 +628,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -663,7 +663,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_number.to_string()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs index f4399d105c5..5996ba05834 100644 --- a/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bamboraapac/transformers.rs @@ -313,7 +313,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -485,7 +485,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -631,7 +631,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -910,7 +910,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(connector_transaction_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs index 11ff53d439c..9804f6c4e6c 100644 --- a/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/bankofamerica/transformers.rs @@ -431,7 +431,7 @@ impl .unwrap_or(info_response.id), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), }, connector_response, @@ -1518,7 +1518,7 @@ fn get_payment_response( .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } } @@ -1816,7 +1816,7 @@ impl .map(|cref| cref.code) .unwrap_or(Some(item.response.id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), connector_response, ..item.data @@ -1833,7 +1833,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), diff --git a/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs b/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs index 05b9abae051..9fe00bac046 100644 --- a/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/billwerk/transformers.rs @@ -291,7 +291,7 @@ impl TryFrom TryFrom TryFrom TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs b/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs index b8253cc411d..e2571e8b58a 100644 --- a/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/braintree/transformers.rs @@ -458,7 +458,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -481,7 +481,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -634,7 +634,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -657,7 +657,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -717,7 +717,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -782,7 +782,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -1287,7 +1287,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -1396,7 +1396,7 @@ impl TryFrom TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ) } @@ -307,7 +307,7 @@ impl TryFrom TryFrom TryFrom TryFrom .custom_id .or(Some(item.response.data.id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; match amount_captured_in_minor_units { diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 5e18ee921b4..b42c02606c1 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -2667,7 +2667,7 @@ fn get_payment_response( .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed, - charge_id: None, + charges: None, }) } } @@ -2761,7 +2761,7 @@ impl .unwrap_or(info_response.id.clone()), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -3171,7 +3171,7 @@ impl network_txn_id: None, connector_response_reference_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -3422,7 +3422,7 @@ impl incremental_authorization_allowed: Some( mandate_status == enums::AttemptStatus::Authorized, ), - charge_id: None, + charges: None, }), }, connector_response, @@ -3540,7 +3540,7 @@ impl .map(|cref| cref.code) .unwrap_or(Some(item.response.id)), incremental_authorization_allowed, - charge_id: None, + charges: None, }), ..item.data }) @@ -3556,7 +3556,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index d2ca3d2d013..f4c3a8ae099 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -428,7 +428,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } DatatransResponse::ThreeDSResponse(response) => { @@ -450,7 +450,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } }; @@ -584,7 +584,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs index b026c339e2a..bf8a7fc9528 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank/transformers.rs @@ -389,7 +389,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(processed.tx_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -421,7 +421,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -594,7 +594,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -645,7 +645,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -895,7 +895,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -984,7 +984,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs index a9408e4e1c5..f67271136f1 100644 --- a/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/digitalvirgo/transformers.rs @@ -167,7 +167,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: Some(response.ssl_txn_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } } @@ -391,7 +391,7 @@ impl TryFrom> for PaymentsSyn network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -450,7 +450,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(response.ssl_txn_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } } diff --git a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs index f4084525410..d6e2cd54db7 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiserv/transformers.rs @@ -385,7 +385,7 @@ impl TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -850,7 +850,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -906,7 +906,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -952,7 +952,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Self { @@ -971,7 +971,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }); Self { response, @@ -1253,7 +1253,7 @@ impl TryFrom> for PaymentsSy .map(|id| id.clone().expose()), connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, @@ -1315,7 +1315,7 @@ impl TryFrom> for PaymentsSy network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, @@ -1486,7 +1486,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, @@ -1599,7 +1599,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, diff --git a/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs b/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs index 2583f47058b..31265162ca0 100644 --- a/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/forte/transformers.rs @@ -308,7 +308,7 @@ impl TryFrom TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id.to_string()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), amount_captured: None, ..item.data @@ -488,7 +488,7 @@ impl TryFrom TryFrom TryFrom redirection_data: Box::new(None), mandate_reference: Box::new(mandate_reference), network_txn_id: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::Charged, ..item.data @@ -687,7 +687,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -718,7 +718,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs b/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs index 4a8ddf68df9..0a5453a49d0 100644 --- a/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/helcim/transformers.rs @@ -387,7 +387,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -438,7 +438,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -487,7 +487,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -567,7 +567,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::from(item.response), ..item.data @@ -624,7 +624,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.invoice_number.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: enums::AttemptStatus::from(item.response), ..item.data diff --git a/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs index 702a7a9b19f..6450839fac3 100644 --- a/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/iatapay/transformers.rs @@ -389,7 +389,7 @@ fn get_iatpay_response( network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, } } None => PaymentsResponseData::TransactionResponse { @@ -400,7 +400,7 @@ fn get_iatpay_response( network_txn_id: None, connector_response_reference_id: connector_response_reference_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }, }; diff --git a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs index 2739aa66bee..3922929892e 100644 --- a/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/inespay/transformers.rs @@ -129,7 +129,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -711,7 +711,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs b/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs index 28584c2a7c4..4bcf4be1ec8 100644 --- a/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/klarna/transformers.rs @@ -393,7 +393,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(response.order_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: get_fraud_status( response.fraud_status.clone(), @@ -414,7 +414,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(response.order_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: get_checkout_status( response.status.clone(), @@ -576,7 +576,7 @@ impl TryFrom TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status, ..item.data diff --git a/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs index f89542d7810..ba2fbc4c301 100644 --- a/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/mifinity/transformers.rs @@ -270,7 +270,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: Some(item.response.operation.order_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -829,7 +829,7 @@ impl response_body.operation.order_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -848,7 +848,7 @@ impl mandate_response.operation.order_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -971,7 +971,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.operation.order_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1143,7 +1143,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.order_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1201,7 +1201,7 @@ impl item.data.request.connector_transaction_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1264,7 +1264,7 @@ impl item.data.request.connector_transaction_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs index bea8b607dae..93b38001576 100644 --- a/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nomupay/transformers.rs @@ -135,7 +135,7 @@ impl TryFrom TryFrom network_txn_id: None, connector_response_reference_id: transaction_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1158,7 +1158,7 @@ impl network_txn_id: None, connector_response_reference_id: transaction_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1327,7 +1327,7 @@ impl network_txn_id: None, connector_response_reference_id: transaction_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs index 84fb7bfecf2..3bc06aa6e19 100644 --- a/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nuvei/transformers.rs @@ -1647,7 +1647,7 @@ where network_txn_id: None, connector_response_reference_id: response.order_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }, ..item.data diff --git a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs index 70635e47300..ef4e308d968 100644 --- a/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/paybox/transformers.rs @@ -702,7 +702,7 @@ impl TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), diff --git a/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs b/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs index 49188803c4f..8437b660a83 100644 --- a/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/payeezy/transformers.rs @@ -447,7 +447,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -411,7 +411,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -459,7 +459,7 @@ impl TryFrom TryFrom TryFrom TryFrom TryFrom TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -807,7 +807,7 @@ impl TryFrom TryFrom TryFrom TryFrom PaymentsResponseDa network_txn_id: None, connector_response_reference_id: Some(connector_response.transaction_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, } } @@ -275,7 +275,7 @@ fn get_payments_sync_response( .clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, } } diff --git a/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs b/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs index a0d349eaa98..86816195276 100644 --- a/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/volt/transformers.rs @@ -289,7 +289,7 @@ impl TryFrom TryFrom TryFrom .map(|cref| cref.code) .unwrap_or(Some(item.response.id)), incremental_authorization_allowed, - charge_id: None, + charges: None, }), ..item.data }) @@ -2082,7 +2082,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), diff --git a/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs index 09d8d8aef7e..f9d7da5d1ec 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldline/transformers.rs @@ -583,7 +583,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -634,7 +634,7 @@ impl TryFrom for Wo network_txn_id: None, connector_response_reference_id: optional_correlation_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..data.clone() }) @@ -531,7 +531,7 @@ impl ConnectorIntegration for Wor network_txn_id: None, connector_response_reference_id: optional_correlation_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..data.clone() }) @@ -632,7 +632,7 @@ impl ConnectorIntegration fo network_txn_id: None, connector_response_reference_id: optional_correlation_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..data.clone() }) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index 47a5d3b2b2b..28cbca418a3 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -771,7 +771,7 @@ impl network_txn_id: network_txn_id.map(|id| id.expose()), connector_response_reference_id: optional_correlation_id.clone(), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), (Some(reason), _) => Err(ErrorResponse { code: worldpay_status.to_string(), diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs index 8ff4b74c076..7a7789e73ab 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -320,7 +320,7 @@ impl item.response.reference_id.peek().to_string(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -378,7 +378,7 @@ impl item.response.reference_id.peek().to_string(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { @@ -427,7 +427,7 @@ impl TryFrom> for Payments network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { diff --git a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs index 14e0b5ba972..abe528eff29 100644 --- a/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/zen/transformers.rs @@ -950,7 +950,7 @@ fn get_zen_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } @@ -994,7 +994,7 @@ impl TryFrom TryFrom TryFrom, pub fingerprint_id: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, // TODO: use a type here instead of value @@ -408,6 +407,8 @@ pub struct PaymentAttempt { pub connector_token_details: Option, /// Indicates the method by which a card is discovered during a payment pub card_discovery: Option, + /// Split payment data + pub charges: Option, } impl PaymentAttempt { @@ -510,7 +511,7 @@ impl PaymentAttempt { authentication_connector: None, authentication_id: None, fingerprint_id: None, - charge_id: None, + charges: None, client_source: None, client_version: None, customer_acceptance: None, @@ -603,6 +604,7 @@ pub struct PaymentAttempt { pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, pub card_discovery: Option, + pub charges: Option, } #[cfg(feature = "v1")] @@ -842,7 +844,6 @@ pub struct PaymentAttemptNew { pub mandate_data: Option, pub payment_method_billing_address_id: Option, pub fingerprint_id: Option, - pub charge_id: Option, pub client_source: Option, pub client_version: Option, pub customer_acceptance: Option, @@ -963,8 +964,8 @@ pub enum PaymentAttemptUpdate { unified_code: Option>, unified_message: Option>, payment_method_data: Option, - charge_id: Option, connector_mandate_detail: Option, + charges: Option, }, UnresolvedResponseUpdate { status: storage_enums::AttemptStatus, @@ -1019,7 +1020,7 @@ pub enum PaymentAttemptUpdate { encoded_data: Option, connector_transaction_id: Option, connector: Option, - charge_id: Option, + charges: Option, updated_by: String, }, IncrementalAuthorizationAmountUpdate { @@ -1235,8 +1236,8 @@ impl PaymentAttemptUpdate { unified_code, unified_message, payment_method_data, - charge_id, connector_mandate_detail, + charges, } => DieselPaymentAttemptUpdate::ResponseUpdate { status, connector, @@ -1257,8 +1258,8 @@ impl PaymentAttemptUpdate { unified_code, unified_message, payment_method_data, - charge_id, connector_mandate_detail, + charges, }, Self::UnresolvedResponseUpdate { status, @@ -1362,14 +1363,14 @@ impl PaymentAttemptUpdate { encoded_data, connector_transaction_id, connector, - charge_id, + charges, updated_by, } => DieselPaymentAttemptUpdate::ConnectorResponse { authentication_data, encoded_data, connector_transaction_id, + charges, connector, - charge_id, updated_by, }, Self::IncrementalAuthorizationAmountUpdate { @@ -1574,6 +1575,7 @@ impl behaviour::Conversion for PaymentAttempt { shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, card_discovery: self.card_discovery, + charges: self.charges, }) } @@ -1656,6 +1658,7 @@ impl behaviour::Conversion for PaymentAttempt { organization_id: storage_model.organization_id, connector_mandate_detail: storage_model.connector_mandate_detail, card_discovery: storage_model.card_discovery, + charges: storage_model.charges, }) } .await @@ -1728,7 +1731,6 @@ impl behaviour::Conversion for PaymentAttempt { mandate_data: self.mandate_data.map(Into::into), fingerprint_id: self.fingerprint_id, payment_method_billing_address_id: self.payment_method_billing_address_id, - charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, customer_acceptance: self.customer_acceptance, @@ -1790,7 +1792,6 @@ impl behaviour::Conversion for PaymentAttempt { authentication_connector, authentication_id, fingerprint_id, - charge_id, client_source, client_version, customer_acceptance, @@ -1807,6 +1808,7 @@ impl behaviour::Conversion for PaymentAttempt { connector, connector_token_details, card_discovery, + charges, } = self; let AttemptAmountDetails { @@ -1866,7 +1868,6 @@ impl behaviour::Conversion for PaymentAttempt { authentication_connector, authentication_id, fingerprint_id, - charge_id, client_source, client_version, customer_acceptance, @@ -1885,6 +1886,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_payment_data, connector_token_details, card_discovery, + charges, }) } @@ -1984,7 +1986,7 @@ impl behaviour::Conversion for PaymentAttempt { authentication_connector: storage_model.authentication_connector, authentication_id: storage_model.authentication_id, fingerprint_id: storage_model.fingerprint_id, - charge_id: storage_model.charge_id, + charges: storage_model.charges, client_source: storage_model.client_source, client_version: storage_model.client_version, customer_acceptance: storage_model.customer_acceptance, @@ -2064,7 +2066,6 @@ impl behaviour::Conversion for PaymentAttempt { authentication_connector: self.authentication_connector, authentication_id: self.authentication_id, fingerprint_id: self.fingerprint_id, - charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, customer_acceptance: self.customer_acceptance, @@ -2082,6 +2083,7 @@ impl behaviour::Conversion for PaymentAttempt { id: self.id, connector_token_details: self.connector_token_details, card_discovery: self.card_discovery, + charges: self.charges, }) } } diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 1ad7d2dcbdd..0c90b0d0244 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -507,7 +507,7 @@ impl network_txn_id, connector_response_reference_id, incremental_authorization_allowed, - charge_id, + charges, } => { let attempt_status = self.get_attempt_status_for_db_update(payment_data); @@ -726,7 +726,7 @@ impl network_txn_id, connector_response_reference_id, incremental_authorization_allowed, - charge_id, + charges, } => { let attempt_status = self.status; @@ -941,7 +941,7 @@ impl network_txn_id, connector_response_reference_id, incremental_authorization_allowed, - charge_id, + charges, } => { let attempt_status = self.get_attempt_status_for_db_update(payment_data); @@ -1160,7 +1160,7 @@ impl network_txn_id, connector_response_reference_id, incremental_authorization_allowed, - charge_id, + charges, } => { let attempt_status = self.get_attempt_status_for_db_update(payment_data); diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 51e4cd5c8ec..13ab97d60dc 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -647,6 +647,7 @@ pub struct RefundIntegrityObject { #[derive(Debug, serde::Deserialize, Clone)] pub enum SplitRefundsRequest { StripeSplitRefund(StripeSplitRefund), + AdyenSplitRefund(common_types::domain::AdyenSplitData), } #[derive(Debug, serde::Deserialize, Clone)] diff --git a/crates/hyperswitch_domain_models/src/router_response_types.rs b/crates/hyperswitch_domain_models/src/router_response_types.rs index 5578647025b..86589ad6801 100644 --- a/crates/hyperswitch_domain_models/src/router_response_types.rs +++ b/crates/hyperswitch_domain_models/src/router_response_types.rs @@ -2,7 +2,7 @@ pub mod disputes; pub mod fraud_check; use std::collections::HashMap; -use common_utils::{request::Method, types as common_types, types::MinorUnit}; +use common_utils::{request::Method, types::MinorUnit}; pub use disputes::{AcceptDisputeResponse, DefendDisputeResponse, SubmitEvidenceResponse}; use crate::{ @@ -26,7 +26,7 @@ pub enum PaymentsResponseData { network_txn_id: Option, connector_response_reference_id: Option, incremental_authorization_allowed: Option, - charge_id: Option, + charges: Option, }, MultipleCaptureResponse { // pending_capture_id_list: Vec, @@ -164,7 +164,7 @@ impl PaymentsResponseData { network_txn_id: auth_network_txn_id, connector_response_reference_id: auth_connector_response_reference_id, incremental_authorization_allowed: auth_incremental_auth_allowed, - charge_id: auth_charge_id, + charges: auth_charges, }, Self::TransactionResponse { resource_id: capture_resource_id, @@ -174,7 +174,7 @@ impl PaymentsResponseData { network_txn_id: capture_network_txn_id, connector_response_reference_id: capture_connector_response_reference_id, incremental_authorization_allowed: capture_incremental_auth_allowed, - charge_id: capture_charge_id, + charges: capture_charges, }, ) => Ok(Self::TransactionResponse { resource_id: capture_resource_id.clone(), @@ -199,7 +199,7 @@ impl PaymentsResponseData { .or(auth_connector_response_reference_id.clone()), incremental_authorization_allowed: (*capture_incremental_auth_allowed) .or(*auth_incremental_auth_allowed), - charge_id: capture_charge_id.clone().or(auth_charge_id.clone()), + charges: auth_charges.clone().or(capture_charges.clone()), }), _ => Err(ApiErrorResponse::NotSupported { message: "Invalid Flow ".to_owned(), @@ -510,7 +510,7 @@ pub struct MandateRevokeResponseData { #[derive(Debug, Clone)] pub enum AuthenticationResponseData { PreAuthVersionCallResponse { - maximum_supported_3ds_version: common_types::SemanticVersion, + maximum_supported_3ds_version: common_utils::types::SemanticVersion, }, PreAuthThreeDsMethodCallResponse { threeds_server_transaction_id: String, diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 026eae764c4..4a4e7f5c8e9 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -218,11 +218,13 @@ Never share your secret api keys. Keep them guarded and secure. common_utils::payout_method_utils::VenmoAdditionalData, common_types::payments::SplitPaymentsRequest, common_types::payments::StripeSplitPaymentRequest, + common_types::domain::AdyenSplitData, + common_types::domain::AdyenSplitItem, common_utils::types::ChargeRefunds, common_types::refunds::SplitRefund, common_types::refunds::StripeSplitRefundRequest, - api_models::payments::SplitPaymentsResponse, - api_models::payments::StripeSplitPaymentsResponse, + common_types::payments::ConnectorChargeResponseData, + common_types::payments::StripeChargeResponseData, api_models::refunds::RefundRequest, api_models::refunds::RefundType, api_models::refunds::RefundResponse, @@ -269,6 +271,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::customers::CustomerResponse, api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, + api_models::enums::AdyenSplitType, api_models::enums::PaymentType, api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index ac88908fbd2..0da00cbfd88 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -159,12 +159,14 @@ Never share your secret api keys. Keep them guarded and secure. common_utils::payout_method_utils::VenmoAdditionalData, common_types::payments::SplitPaymentsRequest, common_types::payments::StripeSplitPaymentRequest, + common_types::domain::AdyenSplitData, + common_types::domain::AdyenSplitItem, common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, common_types::payment_methods::PaymentMethodsEnabled, common_types::refunds::SplitRefund, - api_models::payments::SplitPaymentsResponse, - api_models::payments::StripeSplitPaymentsResponse, + common_types::payments::ConnectorChargeResponseData, + common_types::payments::StripeChargeResponseData, api_models::refunds::RefundRequest, api_models::refunds::RefundsCreateRequest, api_models::refunds::RefundErrorDetails, @@ -221,6 +223,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::customers::CustomerResponse, api_models::admin::AcceptedCountries, api_models::admin::AcceptedCurrencies, + api_models::enums::AdyenSplitType, api_models::enums::ProductType, api_models::enums::PaymentType, api_models::enums::ScaExemptionType, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 1daa1fbaffc..69749500dab 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -171,6 +171,19 @@ pub struct AdyenPaymentRequest<'a> { channel: Option, metadata: Option, merchant_order_reference: Option, + splits: Option>, + store: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +struct AdyenSplitData { + amount: Option, + #[serde(rename = "type")] + split_type: common_enums::AdyenSplitType, + account: Option, + reference: String, + description: Option, } #[serde_with::skip_serializing_none] @@ -365,6 +378,8 @@ pub struct Response { refusal_reason: Option, refusal_reason_code: Option, additional_data: Option, + splits: Option>, + store: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -411,6 +426,8 @@ pub struct RedirectionResponse { refusal_reason_code: Option, psp_reference: Option, merchant_reference: Option, + store: Option, + splits: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -422,6 +439,8 @@ pub struct PresentToShopperResponse { refusal_reason: Option, refusal_reason_code: Option, merchant_reference: Option, + store: Option, + splits: Option>, } #[derive(Debug, Clone, Deserialize, Serialize)] @@ -434,6 +453,8 @@ pub struct QrCodeResponseResponse { additional_data: Option, psp_reference: Option, merchant_reference: Option, + store: Option, + splits: Option>, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -1240,13 +1261,15 @@ pub struct DokuBankData { } // Refunds Request and Response #[serde_with::skip_serializing_none] -#[derive(Default, Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct AdyenRefundRequest { merchant_account: Secret, amount: Amount, merchant_refund_reason: Option, reference: String, + splits: Option>, + store: Option, } #[derive(Default, Debug, Clone, Serialize, Deserialize)] @@ -2754,6 +2777,14 @@ impl } } // }?; + + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; + Ok(AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2781,6 +2812,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -2817,6 +2850,12 @@ impl let payment_method = AdyenPaymentMethod::try_from((card_data, card_holder_name))?; let shopper_email = item.router_data.get_optional_billing_email(); let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; Ok(AdyenPaymentRequest { amount, @@ -2845,6 +2884,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -2874,6 +2915,12 @@ impl let return_url = item.router_data.request.get_router_return_url()?; let payment_method = AdyenPaymentMethod::try_from((bank_debit_data, item.router_data))?; let country_code = get_country_code(item.router_data.get_optional_billing()); + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -2901,6 +2948,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }; Ok(request) } @@ -2933,6 +2982,12 @@ impl let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); let shopper_name = get_shopper_name(item.router_data.get_optional_billing()); + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; let request = AdyenPaymentRequest { amount, @@ -2961,6 +3016,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }; Ok(request) } @@ -2986,6 +3043,12 @@ impl let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let payment_method = AdyenPaymentMethod::try_from((bank_transfer_data, item.router_data))?; let return_url = item.router_data.request.get_router_return_url()?; + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3013,6 +3076,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }; Ok(request) } @@ -3038,6 +3103,13 @@ impl let shopper_interaction = AdyenShopperInteraction::from(item.router_data); let return_url = item.router_data.request.get_router_return_url()?; let payment_method = AdyenPaymentMethod::try_from(gift_card_data)?; + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; + let request = AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3065,6 +3137,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }; Ok(request) } @@ -3101,6 +3175,12 @@ impl let line_items = Some(get_line_items(item)); let billing_address = get_address_info(item.router_data.get_optional_billing()).and_then(Result::ok); + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; Ok(AdyenPaymentRequest { amount, @@ -3129,6 +3209,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -3219,6 +3301,12 @@ impl } else { None }; + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; Ok(AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3246,6 +3334,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -3295,6 +3385,13 @@ impl &billing_address, &delivery_address, ))?; + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; + Ok(AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3322,6 +3419,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -3355,6 +3454,13 @@ impl })? .number .to_owned(); + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; + Ok(AdyenPaymentRequest { amount, merchant_account: auth_type.merchant_account, @@ -3382,6 +3488,8 @@ impl shopper_ip: item.router_data.request.get_ip_address_as_optional(), metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), + store, + splits, }) } } @@ -3397,6 +3505,27 @@ impl TryFrom<&types::PaymentsCancelRouterData> for AdyenCancelRequest { } } +fn get_adyen_split_request( + split_request: &common_types::domain::AdyenSplitData, + currency: common_enums::enums::Currency, +) -> (Option, Option>) { + let splits = split_request + .split_items + .iter() + .map(|split_item| { + let amount = split_item.amount.map(|value| Amount { currency, value }); + AdyenSplitData { + amount, + reference: split_item.reference.clone(), + split_type: split_item.split_type.clone(), + account: split_item.account.clone(), + description: split_item.description.clone(), + } + }) + .collect(); + (split_request.store.clone(), Some(splits)) +} + impl TryFrom> for types::PaymentsCancelRouterData { @@ -3416,7 +3545,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.reference), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -3451,7 +3580,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), payment_method_balance: Some(types::PaymentMethodBalance { currency: item.response.balance.currency, @@ -3513,6 +3642,11 @@ pub fn get_adyen_response( .map(|network_tx_id| network_tx_id.expose()) }); + let charges = match &response.splits { + Some(split_items) => Some(construct_charge_response(response.store, split_items)), + None => None, + }; + let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(response.psp_reference), redirection_data: Box::new(None), @@ -3521,7 +3655,7 @@ pub fn get_adyen_response( network_txn_id, connector_response_reference_id: Some(response.merchant_reference), incremental_authorization_allowed: None, - charge_id: None, + charges, }; Ok((status, error, payments_response_data)) } @@ -3588,7 +3722,7 @@ pub fn get_webhook_response( network_txn_id: None, connector_response_reference_id: Some(response.merchant_reference_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payments_response_data)) } @@ -3650,6 +3784,11 @@ pub fn get_redirection_response( let connector_metadata = get_wait_screen_metadata(&response)?; + let charges = match &response.splits { + Some(split_items) => Some(construct_charge_response(response.store, split_items)), + None => None, + }; + let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: match response.psp_reference.as_ref() { Some(psp) => types::ResponseId::ConnectorTransactionId(psp.to_string()), @@ -3664,7 +3803,7 @@ pub fn get_redirection_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, - charge_id: None, + charges, }; Ok((status, error, payments_response_data)) } @@ -3709,6 +3848,14 @@ pub fn get_present_to_shopper_response( None }; + let charges = match &response.splits { + Some(split_items) => Some(construct_charge_response( + response.store.clone(), + split_items, + )), + None => None, + }; + let connector_metadata = get_present_to_shopper_metadata(&response)?; // We don't get connector transaction id for redirections in Adyen. let payments_response_data = types::PaymentsResponseData::TransactionResponse { @@ -3725,7 +3872,7 @@ pub fn get_present_to_shopper_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, - charge_id: None, + charges, }; Ok((status, error, payments_response_data)) } @@ -3770,6 +3917,14 @@ pub fn get_qr_code_response( None }; + let charges = match &response.splits { + Some(split_items) => Some(construct_charge_response( + response.store.clone(), + split_items, + )), + None => None, + }; + let connector_metadata = get_qr_metadata(&response)?; let payments_response_data = types::PaymentsResponseData::TransactionResponse { resource_id: match response.psp_reference.as_ref() { @@ -3785,7 +3940,7 @@ pub fn get_qr_code_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, - charge_id: None, + charges, }; Ok((status, error, payments_response_data)) } @@ -3828,7 +3983,7 @@ pub fn get_redirection_error_response( .clone() .or(response.psp_reference), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payments_response_data)) @@ -4168,6 +4323,8 @@ pub struct AdyenCaptureResponse { status: String, amount: Amount, merchant_reference: Option, + store: Option, + splits: Option>, } impl TryFrom> @@ -4182,6 +4339,11 @@ impl TryFrom> } else { item.response.payment_psp_reference }; + let charges = match &item.response.splits { + Some(split_items) => Some(construct_charge_response(item.response.store, split_items)), + None => None, + }; + Ok(Self { // From the docs, the only value returned is "received", outcome of refund is available // through refund notification webhook @@ -4195,7 +4357,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.reference), incremental_authorization_allowed: None, - charge_id: None, + charges, }), amount_captured: Some(0), ..item.data @@ -4203,6 +4365,29 @@ impl TryFrom> } } +fn construct_charge_response( + store: Option, + split_item: &[AdyenSplitData], +) -> common_types::payments::ConnectorChargeResponseData { + let splits: Vec = split_item + .iter() + .map(|split_item| common_types::domain::AdyenSplitItem { + amount: split_item.amount.as_ref().map(|amount| amount.value), + reference: split_item.reference.clone(), + split_type: split_item.split_type.clone(), + account: split_item.account.clone(), + description: split_item.description.clone(), + }) + .collect(); + + common_types::payments::ConnectorChargeResponseData::AdyenSplitPayment( + common_types::domain::AdyenSplitData { + store, + split_items: splits, + }, + ) +} + /* // This is a repeated code block from Stripe inegration. Can we avoid the repetition in every integration #[derive(Debug, Serialize, Deserialize)] @@ -4241,6 +4426,16 @@ impl TryFrom<&AdyenRouterData<&types::RefundsRouterData>> for AdyenRefundR type Error = Error; fn try_from(item: &AdyenRouterData<&types::RefundsRouterData>) -> Result { let auth_type = AdyenAuthType::try_from(&item.router_data.connector_auth_type)?; + let (store, splits) = match item + .router_data + .request + .split_refunds + .as_ref() + { + Some(hyperswitch_domain_models::router_request_types::SplitRefundsRequest::AdyenSplitRefund(adyen_split_data)) => get_adyen_split_request(adyen_split_data, item.router_data.request.currency), + _ => (None, None), + }; + Ok(Self { merchant_account: auth_type.merchant_account, amount: Amount { @@ -4249,6 +4444,8 @@ impl TryFrom<&AdyenRouterData<&types::RefundsRouterData>> for AdyenRefundR }, merchant_refund_reason: item.router_data.request.reason.clone(), reference: item.router_data.request.refund_id.clone(), + store, + splits, }) } } @@ -5458,6 +5655,12 @@ impl .unwrap_or_default(), eci: Some("02".to_string()), }; + let (store, splits) = match item.router_data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => get_adyen_split_request(adyen_split_payment, item.router_data.request.currency), + _ => (None, None), + }; Ok(AdyenPaymentRequest { amount, @@ -5486,6 +5689,8 @@ impl metadata: item.router_data.request.metadata.clone().map(Into::into), merchant_order_reference: item.router_data.request.merchant_order_reference_id.clone(), mpi_data: Some(mpi_data), + store, + splits, }) } } diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 276ea6bf77b..3be29293972 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -444,7 +444,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -1205,7 +1205,7 @@ impl transaction_response.transaction_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), }, ..item.data @@ -1278,7 +1278,7 @@ impl transaction_response.transaction_id.clone(), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), }, ..item.data @@ -1603,7 +1603,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(transaction.transaction_id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: payment_status, ..item.data diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 321a8cec765..254c0346e8e 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -708,7 +708,7 @@ impl TryFrom> item.response.reference.unwrap_or(item.response.id), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, @@ -761,7 +761,7 @@ impl TryFrom> item.response.reference.unwrap_or(item.response.id), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok(Self { status, @@ -837,7 +837,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status: response.into(), ..item.data @@ -938,7 +938,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: item.response.reference, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), status, amount_captured, diff --git a/crates/router/src/connector/dummyconnector/transformers.rs b/crates/router/src/connector/dummyconnector/transformers.rs index 79caa3d0a76..9d745ca1978 100644 --- a/crates/router/src/connector/dummyconnector/transformers.rs +++ b/crates/router/src/connector/dummyconnector/transformers.rs @@ -259,7 +259,7 @@ impl TryFrom network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), enums::AttemptStatus::Charged, ), @@ -903,7 +903,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), if let Some(diesel_models::enums::CaptureMethod::Automatic) = item.data.request.capture_method @@ -954,7 +954,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.orderid), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), enums::AttemptStatus::VoidInitiated, ), @@ -1005,7 +1005,7 @@ impl TryFrom network_txn_id: None, connector_response_reference_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } }, diff --git a/crates/router/src/connector/opayo/transformers.rs b/crates/router/src/connector/opayo/transformers.rs index f55aa4325b0..992acfb5ef0 100644 --- a/crates/router/src/connector/opayo/transformers.rs +++ b/crates/router/src/connector/opayo/transformers.rs @@ -151,7 +151,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.transaction_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/router/src/connector/opennode/transformers.rs b/crates/router/src/connector/opennode/transformers.rs index 44315ac42fa..067237bc3e2 100644 --- a/crates/router/src/connector/opennode/transformers.rs +++ b/crates/router/src/connector/opennode/transformers.rs @@ -144,7 +144,7 @@ impl network_txn_id: None, connector_response_reference_id: item.response.data.order_id, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } else { Ok(types::PaymentsResponseData::TransactionUnresolvedResponse { diff --git a/crates/router/src/connector/payme/transformers.rs b/crates/router/src/connector/payme/transformers.rs index c13730529e2..ec9991b4815 100644 --- a/crates/router/src/connector/payme/transformers.rs +++ b/crates/router/src/connector/payme/transformers.rs @@ -259,7 +259,7 @@ impl TryFrom<&PaymePaySaleResponse> for types::PaymentsResponseData { network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) } } @@ -326,7 +326,7 @@ impl From<&SaleQuery> for types::PaymentsResponseData { network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, } } } @@ -547,7 +547,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }), @@ -1128,7 +1128,7 @@ impl TryFrom> network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; Ok(Self { diff --git a/crates/router/src/connector/paypal.rs b/crates/router/src/connector/paypal.rs index fb635708c23..c7aef2ddb23 100644 --- a/crates/router/src/connector/paypal.rs +++ b/crates/router/src/connector/paypal.rs @@ -1227,7 +1227,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..data.clone() }) @@ -1278,7 +1278,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..data.clone() }) diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 884e67714ff..5989706bf5b 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -622,7 +622,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(info_response.id.clone()), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1859,7 +1859,7 @@ impl .clone() .or(Some(item.response.id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -1984,7 +1984,7 @@ impl purchase_units.map_or(item.response.id, |item| item.invoice_id.clone()), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2039,7 +2039,7 @@ impl purchase_units.map_or(item.response.id, |item| item.invoice_id.clone()), ), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2092,7 +2092,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2130,7 +2130,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2183,7 +2183,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2252,7 +2252,7 @@ impl .clone() .or(Some(item.response.supplementary_data.related_ids.order_id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) @@ -2593,7 +2593,7 @@ impl TryFrom> .invoice_id .or(Some(item.response.id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), amount_captured: Some(amount_captured), ..item.data @@ -2645,7 +2645,7 @@ impl .invoice_id .or(Some(item.response.id)), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/router/src/connector/plaid/transformers.rs b/crates/router/src/connector/plaid/transformers.rs index 29d4db1297f..4e4fb1863ee 100644 --- a/crates/router/src/connector/plaid/transformers.rs +++ b/crates/router/src/connector/plaid/transformers.rs @@ -314,7 +314,7 @@ impl network_txn_id: None, connector_response_reference_id: Some(item.response.payment_id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }, ..item.data @@ -400,7 +400,7 @@ impl TryFrom { - transformers::transform_headers_for_connect_platform( - stripe_split_payment.charge_type.clone(), - stripe_split_payment.transfer_account_id.clone(), - &mut header, - ); - } - } + if let Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + )) = &req.request.split_payments + { + transformers::transform_headers_for_connect_platform( + stripe_split_payment.charge_type.clone(), + stripe_split_payment.transfer_account_id.clone(), + &mut header, + ); } Ok(header) } @@ -943,26 +940,21 @@ impl let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); - if let Some(split_payments) = &req.request.split_payments { - match split_payments { - common_types::payments::SplitPaymentsRequest::StripeSplitPayment( - stripe_split_payment, - ) => { - if stripe_split_payment.charge_type - == api::enums::PaymentChargeType::Stripe( - api::enums::StripeChargeType::Direct, - ) - { - let mut customer_account_header = vec![( - headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), - stripe_split_payment - .transfer_account_id - .clone() - .into_masked(), - )]; - header.append(&mut customer_account_header); - } - } + if let Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + )) = &req.request.split_payments + { + if stripe_split_payment.charge_type + == api::enums::PaymentChargeType::Stripe(api::enums::StripeChargeType::Direct) + { + let mut customer_account_header = vec![( + headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), + stripe_split_payment + .transfer_account_id + .clone() + .into_masked(), + )]; + header.append(&mut customer_account_header); } } Ok(header) @@ -1484,22 +1476,20 @@ impl services::ConnectorIntegration { - match &stripe_split_refund.charge_type { - api::enums::PaymentChargeType::Stripe(stripe_charge) => { - if stripe_charge == &api::enums::StripeChargeType::Direct { - let mut customer_account_header = vec![( - headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), - stripe_split_refund - .transfer_account_id - .clone() - .into_masked(), - )]; - header.append(&mut customer_account_header); - } - } + if let Some(SplitRefundsRequest::StripeSplitRefund(ref stripe_split_refund)) = + req.request.split_refunds.as_ref() + { + match &stripe_split_refund.charge_type { + api::enums::PaymentChargeType::Stripe(stripe_charge) => { + if stripe_charge == &api::enums::StripeChargeType::Direct { + let mut customer_account_header = vec![( + headers::STRIPE_COMPATIBLE_CONNECT_ACCOUNT.to_string(), + stripe_split_refund + .transfer_account_id + .clone() + .into_masked(), + )]; + header.append(&mut customer_account_header); } } } @@ -1530,15 +1520,15 @@ impl services::ConnectorIntegration RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( - req, - refund_amount, - ))?)), - Some(split_refunds) => match split_refunds { - SplitRefundsRequest::StripeSplitRefund(_) => RequestContent::FormUrlEncoded( - Box::new(stripe::ChargeRefundRequest::try_from(req)?), - ), - }, + Some(SplitRefundsRequest::AdyenSplitRefund(_)) | None => { + RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( + req, + refund_amount, + ))?)) + } + Some(SplitRefundsRequest::StripeSplitRefund(_)) => RequestContent::FormUrlEncoded( + Box::new(stripe::ChargeRefundRequest::try_from(req)?), + ), }; Ok(request_body) } @@ -1653,16 +1643,14 @@ impl services::ConnectorIntegration { - transformers::transform_headers_for_connect_platform( - stripe_refund.charge_type.clone(), - stripe_refund.transfer_account_id.clone(), - &mut header, - ); - } - } + if let Some(SplitRefundsRequest::StripeSplitRefund(ref stripe_refund)) = + req.request.split_refunds.as_ref() + { + transformers::transform_headers_for_connect_platform( + stripe_refund.charge_type.clone(), + stripe_refund.transfer_account_id.clone(), + &mut header, + ); } Ok(header) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index a93ba6ecaa5..7611f375c09 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1973,7 +1973,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }; (charges, None) } - None => (None, item.connector_customer.to_owned().map(Secret::new)), + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) | None => { + (None, item.connector_customer.to_owned().map(Secret::new)) + } }; Ok(Self { @@ -2473,6 +2475,8 @@ fn extract_payment_method_connector_response_from_latest_attempt( impl TryFrom> for types::RouterData +where + T: connector_util::SplitPaymentData, { type Error = error_stack::Report; fn try_from( @@ -2526,14 +2530,16 @@ impl item.response.id.clone(), )) } else { - let charge_id = item + let charges = item .response .latest_charge .as_ref() .map(|charge| match charge { - StripeChargeEnum::ChargeId(charge_id) => charge_id.clone(), + StripeChargeEnum::ChargeId(charges) => charges.clone(), StripeChargeEnum::ChargeObject(charge) => charge.id.clone(), - }); + }) + .and_then(|charge_id| construct_charge_response(charge_id, &item.data.request)); + Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(redirection_data), @@ -2542,7 +2548,7 @@ impl network_txn_id, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id, + charges, }) }; @@ -2631,6 +2637,8 @@ pub fn get_connector_metadata( impl TryFrom> for types::RouterData +where + T: connector_util::SplitPaymentData, { type Error = error_stack::Report; fn try_from( @@ -2735,14 +2743,16 @@ impl }), _ => None, }; - let charge_id = item + let charges = item .response .latest_charge .as_ref() .map(|charge| match charge { - StripeChargeEnum::ChargeId(charge_id) => charge_id.clone(), + StripeChargeEnum::ChargeId(charges) => charges.clone(), StripeChargeEnum::ChargeObject(charge) => charge.id.clone(), - }); + }) + .and_then(|charge_id| construct_charge_response(charge_id, &item.data.request)); + Ok(types::PaymentsResponseData::TransactionResponse { resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: Box::new(redirection_data), @@ -2751,7 +2761,7 @@ impl network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id.clone()), incremental_authorization_allowed: None, - charge_id, + charges, }) }; @@ -2831,7 +2841,7 @@ impl network_txn_id: network_transaction_id, connector_response_reference_id: Some(item.response.id), incremental_authorization_allowed: None, - charge_id: None, + charges: None, }) }; @@ -3082,6 +3092,11 @@ impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { }, }) } + types::SplitRefundsRequest::AdyenSplitRefund(_) => { + Err(errors::ConnectorError::MissingRequiredField { + field_name: "stripe_split_refund", + })? + } }, } } @@ -3543,7 +3558,7 @@ impl TryFrom( + charge_id: String, + request: &T, +) -> Option +where + T: connector_util::SplitPaymentData, +{ + let charge_request = request.get_split_payment_data(); + if let Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + )) = charge_request + { + let stripe_charge_response = common_types::payments::StripeChargeResponseData { + charge_id: Some(charge_id), + charge_type: stripe_split_payment.charge_type, + application_fees: stripe_split_payment.application_fees, + transfer_account_id: stripe_split_payment.transfer_account_id, + }; + Some( + common_types::payments::ConnectorChargeResponseData::StripeSplitPayment( + stripe_charge_response, + ), + ) + } else { + None + } +} + #[cfg(test)] mod test_validate_shipping_address_against_payment_method { #![allow(clippy::unwrap_used)] diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 31c992a7f5a..78641d7e33b 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -738,7 +738,7 @@ fn handle_cards_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } @@ -768,7 +768,7 @@ fn handle_bank_redirects_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } @@ -802,7 +802,7 @@ fn handle_bank_redirects_error_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } @@ -863,7 +863,7 @@ fn handle_bank_redirects_sync_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } @@ -911,7 +911,7 @@ pub fn handle_webhook_response( network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }; Ok((status, error, payment_response_data)) } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index eff68176f8c..28f5d016e5a 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -753,6 +753,40 @@ impl PaymentsCaptureRequestData for types::PaymentsCaptureData { } } +pub trait SplitPaymentData { + fn get_split_payment_data(&self) -> Option; +} + +impl SplitPaymentData for types::PaymentsCaptureData { + fn get_split_payment_data(&self) -> Option { + None + } +} + +impl SplitPaymentData for types::PaymentsAuthorizeData { + fn get_split_payment_data(&self) -> Option { + self.split_payments.clone() + } +} + +impl SplitPaymentData for types::PaymentsSyncData { + fn get_split_payment_data(&self) -> Option { + self.split_payments.clone() + } +} + +impl SplitPaymentData for PaymentsCancelData { + fn get_split_payment_data(&self) -> Option { + None + } +} + +impl SplitPaymentData for types::SetupMandateRequestData { + fn get_split_payment_data(&self) -> Option { + None + } +} + pub trait RevokeMandateRequestData { fn get_connector_mandate_id(&self) -> Result; } diff --git a/crates/router/src/connector/wellsfargopayout/transformers.rs b/crates/router/src/connector/wellsfargopayout/transformers.rs index 135cb5f53cb..3a194550dfd 100644 --- a/crates/router/src/connector/wellsfargopayout/transformers.rs +++ b/crates/router/src/connector/wellsfargopayout/transformers.rs @@ -140,7 +140,7 @@ impl network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }), ..item.data }) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index db12a6282c3..cfa57a35654 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -4285,7 +4285,6 @@ impl AttemptType { // New payment method billing address can be passed for a retry payment_method_billing_address_id: None, fingerprint_id: None, - charge_id: None, client_source: old_payment_attempt.client_source, client_version: old_payment_attempt.client_version, customer_acceptance: old_payment_attempt.customer_acceptance, @@ -6882,15 +6881,14 @@ pub async fn validate_merchant_connector_ids_in_connector_mandate_details( Ok(()) } -pub fn validate_platform_fees_for_marketplace( +pub fn validate_platform_request_for_marketplace( amount: api::Amount, split_payments: Option, ) -> Result<(), errors::ApiErrorResponse> { - if let Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( - stripe_split_payment, - )) = split_payments - { - match amount { + match split_payments { + Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment, + )) => match amount { api::Amount::Zero => { if stripe_split_payment.application_fees.get_amount_as_i64() != 0 { return Err(errors::ApiErrorResponse::InvalidDataValue { @@ -6905,7 +6903,90 @@ pub fn validate_platform_fees_for_marketplace( }); } } + }, + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment( + adyen_split_payment, + )) => { + let total_split_amount: i64 = adyen_split_payment + .split_items + .iter() + .map(|split_item| { + split_item + .amount + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64() + }) + .sum(); + + match amount { + api::Amount::Zero => { + if total_split_amount != 0 { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "Sum of split amounts should be equal to the total amount", + }); + } + } + api::Amount::Value(amount) => { + let i64_amount: i64 = amount.into(); + if !adyen_split_payment.split_items.is_empty() + && i64_amount != total_split_amount + { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Sum of split amounts should be equal to the total amount" + .to_string(), + }); + } + } + }; + adyen_split_payment + .split_items + .iter() + .try_for_each(|split_item| { + match split_item.split_type { + common_enums::AdyenSplitType::BalanceAccount => { + if split_item.account.is_none() { + return Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: + "split_payments.adyen_split_payment.split_items.account", + }); + } + } + common_enums::AdyenSplitType::Commission + | enums::AdyenSplitType::Vat + | enums::AdyenSplitType::TopUp => { + if split_item.amount.is_none() { + return Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: + "split_payments.adyen_split_payment.split_items.amount", + }); + } + if let enums::AdyenSplitType::TopUp = split_item.split_type { + if split_item.account.is_none() { + return Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: + "split_payments.adyen_split_payment.split_items.account", + }); + } + if adyen_split_payment.store.is_some() { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: "Topup split payment is not available via Adyen Platform" + .to_string(), + }); + } + } + } + enums::AdyenSplitType::AcquiringFees + | enums::AdyenSplitType::PaymentFee + | enums::AdyenSplitType::AdyenFees + | enums::AdyenSplitType::AdyenCommission + | enums::AdyenSplitType::AdyenMarkup + | enums::AdyenSplitType::Interchange + | enums::AdyenSplitType::SchemeFee => {} + }; + Ok(()) + })?; } + None => (), } Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index dd72bfff307..1fd9f2404ff 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1038,7 +1038,7 @@ impl ValidateRequest( connector_metadata, connector_response_reference_id, incremental_authorization_allowed, - charge_id, + charges, .. } => { payment_data @@ -1720,11 +1720,11 @@ async fn payment_response_update_tracker( authentication_data, encoded_data, payment_method_data: additional_payment_method_data, - charge_id, connector_mandate_detail: payment_data .payment_attempt .connector_mandate_detail .clone(), + charges, }), ), }; diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 0401575bc66..1a0a7ad9e5d 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -420,7 +420,7 @@ where resource_id, connector_metadata, redirection_data, - charge_id, + charges, .. }) => { let encoded_data = payment_data.get_payment_attempt().encoded_data.clone(); @@ -465,8 +465,8 @@ where unified_code: None, unified_message: None, payment_method_data: additional_payment_method_data, - charge_id, connector_mandate_detail: None, + charges, }; #[cfg(feature = "v1")] @@ -651,7 +651,6 @@ pub fn make_new_payment_attempt( mandate_data: Default::default(), payment_method_billing_address_id: Default::default(), fingerprint_id: Default::default(), - charge_id: Default::default(), customer_acceptance: Default::default(), connector_mandate_detail: Default::default(), card_discovery: old_payment_attempt.card_discovery, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 1e9adb0a06b..e2967e4ba59 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1082,7 +1082,7 @@ where network_txn_id: None, connector_response_reference_id: None, incremental_authorization_allowed: None, - charge_id: None, + charges: None, }); let additional_data = PaymentAdditionalData { @@ -2300,24 +2300,6 @@ where ) }); - let split_payments_response = match payment_intent.split_payments { - None => None, - Some(split_payments) => match split_payments { - common_types::payments::SplitPaymentsRequest::StripeSplitPayment( - stripe_split_payment, - ) => Some( - api_models::payments::SplitPaymentsResponse::StripeSplitPayment( - api_models::payments::StripeSplitPaymentsResponse { - charge_id: payment_attempt.charge_id.clone(), - charge_type: stripe_split_payment.charge_type, - application_fees: stripe_split_payment.application_fees, - transfer_account_id: stripe_split_payment.transfer_account_id, - }, - ), - ), - }, - }; - let mandate_data = payment_data.get_setup_mandate().map(|d| api::MandateData { customer_acceptance: d .customer_acceptance @@ -2496,7 +2478,7 @@ where .get_payment_method_info() .map(|info| info.status), updated: Some(payment_intent.modified_at), - split_payments: split_payments_response, + split_payments: payment_attempt.charges, frm_metadata: payment_intent.frm_metadata, merchant_order_reference_id: payment_intent.merchant_order_reference_id, order_tax_amount, diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index a6505f23631..4d8cc195693 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -13,8 +13,7 @@ use common_utils::{ use diesel_models::process_tracker::business_status; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ - router_data::ErrorResponse, - router_request_types::{SplitRefundsRequest, StripeSplitRefund}, + router_data::ErrorResponse, router_request_types::SplitRefundsRequest, }; use hyperswitch_interfaces::integrity::{CheckIntegrity, FlowIntegrity, GetIntegrityObject}; use router_env::{instrument, tracing}; @@ -514,18 +513,12 @@ pub async fn refund_retrieve_core( .await .transpose()?; - let split_refunds_req: Option = payment_intent - .split_payments - .clone() - .zip(refund.split_refunds.clone()) - .map(|(split_payments, split_refunds)| { - SplitRefundsRequest::try_from(SplitRefundInput { - refund_request: split_refunds, - payment_charges: split_payments, - charge_id: payment_attempt.charge_id.clone(), - }) - }) - .transpose()?; + let split_refunds_req = core_utils::get_split_refunds(SplitRefundInput { + split_payment_request: payment_intent.split_payments.clone(), + payment_charges: payment_attempt.charges.clone(), + charge_id: payment_attempt.charge_id.clone(), + refund_request: refund.split_refunds.clone(), + })?; let unified_translated_message = if let (Some(unified_code), Some(unified_message)) = (refund.unified_code.clone(), refund.unified_message.clone()) @@ -814,32 +807,12 @@ pub async fn validate_and_create_refund( creds_identifier: Option, ) -> RouterResult { let db = &*state.store; - - let split_refunds = match payment_intent.split_payments.as_ref() { - Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment(stripe_payment)) => { - if let Some(charge_id) = payment_attempt.charge_id.clone() { - let refund_request = req - .split_refunds - .clone() - .get_required_value("split_refunds")?; - - let options = validator::validate_charge_refund( - &refund_request, - &stripe_payment.charge_type, - )?; - - Some(SplitRefundsRequest::StripeSplitRefund(StripeSplitRefund { - charge_id, - charge_type: stripe_payment.charge_type.clone(), - transfer_account_id: stripe_payment.transfer_account_id.clone(), - options, - })) - } else { - None - } - } - _ => None, - }; + let split_refunds = core_utils::get_split_refunds(SplitRefundInput { + split_payment_request: payment_intent.split_payments.clone(), + payment_charges: payment_attempt.charges.clone(), + charge_id: payment_attempt.charge_id.clone(), + refund_request: req.split_refunds.clone(), + })?; // Only for initial dev and testing let refund_type = req.refund_type.unwrap_or_default(); @@ -1554,36 +1527,12 @@ pub async fn trigger_refund_execute_workflow( ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; - - let split_refunds = match payment_intent.split_payments.as_ref() { - Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( - stripe_payment, - )) => { - let refund_request = refund - .split_refunds - .clone() - .get_required_value("split_refunds")?; - - let options = validator::validate_charge_refund( - &refund_request, - &stripe_payment.charge_type, - )?; - - let charge_id = payment_attempt.charge_id.clone().ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError).attach_printable( - "Transaction is invalid. Missing field \"charge_id\" in payment_attempt.", - ) - })?; - - Some(SplitRefundsRequest::StripeSplitRefund(StripeSplitRefund { - charge_id, - charge_type: stripe_payment.charge_type.clone(), - transfer_account_id: stripe_payment.transfer_account_id.clone(), - options, - })) - } - _ => None, - }; + let split_refunds = core_utils::get_split_refunds(SplitRefundInput { + split_payment_request: payment_intent.split_payments.clone(), + payment_charges: payment_attempt.charges.clone(), + charge_id: payment_attempt.charge_id.clone(), + refund_request: refund.split_refunds.clone(), + })?; //trigger refund request to gateway let updated_refund = Box::pin(trigger_refund_to_gateway( diff --git a/crates/router/src/core/refunds/transformers.rs b/crates/router/src/core/refunds/transformers.rs index 256e4c34522..151c6d20234 100644 --- a/crates/router/src/core/refunds/transformers.rs +++ b/crates/router/src/core/refunds/transformers.rs @@ -1,54 +1,6 @@ -use error_stack::{report, Report}; -use hyperswitch_domain_models::router_request_types; - -use super::validator; -use crate::core::errors; - pub struct SplitRefundInput { - pub refund_request: common_types::refunds::SplitRefund, - pub payment_charges: common_types::payments::SplitPaymentsRequest, + pub refund_request: Option, + pub payment_charges: Option, + pub split_payment_request: Option, pub charge_id: Option, } - -impl TryFrom for router_request_types::SplitRefundsRequest { - type Error = Report; - - fn try_from(value: SplitRefundInput) -> Result { - let SplitRefundInput { - refund_request, - payment_charges, - charge_id, - } = value; - - match refund_request { - common_types::refunds::SplitRefund::StripeSplitRefund(stripe_refund) => { - match payment_charges { - common_types::payments::SplitPaymentsRequest::StripeSplitPayment( - stripe_payment, - ) => { - let charge_id = charge_id.ok_or_else(|| { - report!(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Missing `charge_id` in PaymentAttempt.") - })?; - - let options = validator::validate_charge_refund( - &common_types::refunds::SplitRefund::StripeSplitRefund( - stripe_refund.clone(), - ), - &stripe_payment.charge_type, - )?; - - Ok(Self::StripeSplitRefund( - router_request_types::StripeSplitRefund { - charge_id, // Use `charge_id` from `PaymentAttempt` - transfer_account_id: stripe_payment.transfer_account_id, - charge_type: stripe_payment.charge_type, - options, - }, - )) - } - } - } - } - } -} diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 2387f9a3b5f..90c4a7c4434 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -146,35 +146,109 @@ pub fn validate_for_valid_refunds( } } -pub fn validate_charge_refund( - charges: &common_types::refunds::SplitRefund, - charge_type: &api_enums::PaymentChargeType, +pub fn validate_stripe_charge_refund( + charge_type_option: Option, + split_refund_request: &Option, ) -> RouterResult { - match charge_type { - api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Direct) => { - let common_types::refunds::SplitRefund::StripeSplitRefund(stripe_charge) = charges; + let charge_type = charge_type_option.ok_or_else(|| { + report!(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Missing `charge_type` in PaymentAttempt.") + })?; + + let refund_request = match split_refund_request { + Some(common_types::refunds::SplitRefund::StripeSplitRefund(stripe_split_refund)) => { + stripe_split_refund + } + _ => Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "stripe_split_refund", + })?, + }; - Ok(types::ChargeRefundsOptions::Direct( - types::DirectChargeRefund { - revert_platform_fee: stripe_charge - .revert_platform_fee - .get_required_value("revert_platform_fee")?, - }, - )) + let options = match charge_type { + api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Direct) => { + types::ChargeRefundsOptions::Direct(types::DirectChargeRefund { + revert_platform_fee: refund_request + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + }) } api_enums::PaymentChargeType::Stripe(api_enums::StripeChargeType::Destination) => { - let common_types::refunds::SplitRefund::StripeSplitRefund(stripe_charge) = charges; - - Ok(types::ChargeRefundsOptions::Destination( - types::DestinationChargeRefund { - revert_platform_fee: stripe_charge - .revert_platform_fee - .get_required_value("revert_platform_fee")?, - revert_transfer: stripe_charge - .revert_transfer - .get_required_value("revert_transfer")?, - }, - )) + types::ChargeRefundsOptions::Destination(types::DestinationChargeRefund { + revert_platform_fee: refund_request + .revert_platform_fee + .get_required_value("revert_platform_fee")?, + revert_transfer: refund_request + .revert_transfer + .get_required_value("revert_transfer")?, + }) + } + }; + + Ok(options) +} + +pub fn validate_adyen_charge_refund( + adyen_split_payment_response: &common_types::domain::AdyenSplitData, + adyen_split_refund_request: &common_types::domain::AdyenSplitData, +) -> RouterResult<()> { + if adyen_split_refund_request.store != adyen_split_payment_response.store { + return Err(report!(errors::ApiErrorResponse::InvalidDataValue { + field_name: "split_payments.adyen_split_payment.store", + })); + }; + + for refund_split_item in adyen_split_refund_request.split_items.iter() { + let refund_split_reference = refund_split_item.reference.clone(); + let matching_payment_split_item = adyen_split_payment_response + .split_items + .iter() + .find(|payment_split_item| refund_split_reference == payment_split_item.reference); + + if let Some(payment_split_item) = matching_payment_split_item { + if let Some((refund_amount, payment_amount)) = + refund_split_item.amount.zip(payment_split_item.amount) + { + if refund_amount > payment_amount { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: format!( + "Invalid refund amount for split item, reference: {}", + refund_split_reference + ), + })); + } + } + + if let Some((refund_account, payment_account)) = refund_split_item + .account + .as_ref() + .zip(payment_split_item.account.as_ref()) + { + if !refund_account.eq(payment_account) { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: format!( + "Invalid refund account for split item, reference: {}", + refund_split_reference + ), + })); + } + } + + if refund_split_item.split_type != payment_split_item.split_type { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: format!( + "Invalid refund split_type for split item, reference: {}", + refund_split_reference + ), + })); + } + } else { + return Err(report!(errors::ApiErrorResponse::InvalidRequestData { + message: format!( + "No matching payment split item found for reference: {}", + refund_split_reference + ), + })); } } + Ok(()) } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 71cc3dceebb..cd2a9ed220b 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -461,6 +461,86 @@ pub fn validate_uuid(uuid: String, key: &str) -> Result RouterResult> { + match split_refund_input.split_payment_request.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment(stripe_payment)) => { + let (charge_id_option, charge_type_option) = match ( + &split_refund_input.payment_charges, + &split_refund_input.split_payment_request, + ) { + ( + Some(common_types::payments::ConnectorChargeResponseData::StripeSplitPayment( + stripe_split_payment_response, + )), + _, + ) => ( + stripe_split_payment_response.charge_id.clone(), + Some(stripe_split_payment_response.charge_type.clone()), + ), + ( + _, + Some(common_types::payments::SplitPaymentsRequest::StripeSplitPayment( + stripe_split_payment_request, + )), + ) => ( + split_refund_input.charge_id, + Some(stripe_split_payment_request.charge_type.clone()), + ), + (_, _) => (None, None), + }; + + if let Some(charge_id) = charge_id_option { + let options = super::refunds::validator::validate_stripe_charge_refund( + charge_type_option, + &split_refund_input.refund_request, + )?; + + Ok(Some( + router_request_types::SplitRefundsRequest::StripeSplitRefund( + router_request_types::StripeSplitRefund { + charge_id, + charge_type: stripe_payment.charge_type.clone(), + transfer_account_id: stripe_payment.transfer_account_id.clone(), + options, + }, + ), + )) + } else { + Ok(None) + } + } + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) => { + match &split_refund_input.payment_charges { + Some(common_types::payments::ConnectorChargeResponseData::AdyenSplitPayment( + adyen_split_payment_response, + )) => { + if let Some(common_types::refunds::SplitRefund::AdyenSplitRefund( + split_refund_request, + )) = split_refund_input.refund_request.clone() + { + super::refunds::validator::validate_adyen_charge_refund( + adyen_split_payment_response, + &split_refund_request, + )?; + + Ok(Some( + router_request_types::SplitRefundsRequest::AdyenSplitRefund( + split_refund_request, + ), + )) + } else { + Ok(None) + } + } + _ => Ok(None), + } + } + _ => Ok(None), + } +} #[cfg(test)] mod tests { #![allow(clippy::expect_used)] diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 5958435abc7..1773d7f3a17 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -210,7 +210,6 @@ mod tests { mandate_data: Default::default(), payment_method_billing_address_id: Default::default(), fingerprint_id: Default::default(), - charge_id: Default::default(), client_source: Default::default(), client_version: Default::default(), customer_acceptance: Default::default(), @@ -295,7 +294,6 @@ mod tests { mandate_data: Default::default(), payment_method_billing_address_id: Default::default(), fingerprint_id: Default::default(), - charge_id: Default::default(), client_source: Default::default(), client_version: Default::default(), customer_acceptance: Default::default(), @@ -393,7 +391,6 @@ mod tests { mandate_data: Default::default(), payment_method_billing_address_id: Default::default(), fingerprint_id: Default::default(), - charge_id: Default::default(), client_source: Default::default(), client_version: Default::default(), customer_acceptance: Default::default(), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index fd84f5767de..427cba5eb97 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1134,7 +1134,7 @@ pub fn get_connector_metadata( network_txn_id: _, connector_response_reference_id: _, incremental_authorization_allowed: _, - charge_id: _, + charges: _, }) => connector_metadata, _ => None, } diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 3a45cfad9a2..db210dbdbe5 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -164,6 +164,7 @@ impl PaymentAttemptInterface for MockDb { payment_token: None, error_code: payment_attempt.error_code, connector_metadata: None, + charge_id: None, payment_experience: payment_attempt.payment_experience, payment_method_type: payment_attempt.payment_method_type, payment_method_data: payment_attempt.payment_method_data, @@ -188,7 +189,6 @@ impl PaymentAttemptInterface for MockDb { mandate_data: payment_attempt.mandate_data, payment_method_billing_address_id: payment_attempt.payment_method_billing_address_id, fingerprint_id: payment_attempt.fingerprint_id, - charge_id: payment_attempt.charge_id, client_source: payment_attempt.client_source, client_version: payment_attempt.client_version, customer_acceptance: payment_attempt.customer_acceptance, @@ -196,6 +196,7 @@ impl PaymentAttemptInterface for MockDb { profile_id: payment_attempt.profile_id, connector_mandate_detail: payment_attempt.connector_mandate_detail, card_discovery: payment_attempt.card_discovery, + charges: None, }; payment_attempts.push(payment_attempt.clone()); Ok(payment_attempt) diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 3480f8f4ba4..8a8e8d4e4a8 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -541,6 +541,7 @@ impl PaymentAttemptInterface for KVRouterStore { error_reason: payment_attempt.error_reason.clone(), multiple_capture_count: payment_attempt.multiple_capture_count, connector_response_reference_id: None, + charge_id: None, amount_capturable: payment_attempt.amount_capturable, updated_by: storage_scheme.to_string(), authentication_data: payment_attempt.authentication_data.clone(), @@ -557,7 +558,6 @@ impl PaymentAttemptInterface for KVRouterStore { .payment_method_billing_address_id .clone(), fingerprint_id: payment_attempt.fingerprint_id.clone(), - charge_id: payment_attempt.charge_id.clone(), client_source: payment_attempt.client_source.clone(), client_version: payment_attempt.client_version.clone(), customer_acceptance: payment_attempt.customer_acceptance.clone(), @@ -565,6 +565,7 @@ impl PaymentAttemptInterface for KVRouterStore { profile_id: payment_attempt.profile_id.clone(), connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), card_discovery: payment_attempt.card_discovery, + charges: None, }; let field = format!("pa_{}", created_attempt.attempt_id); @@ -1513,6 +1514,7 @@ impl DataModelExt for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, card_discovery: self.card_discovery, + charges: self.charges, } } @@ -1590,6 +1592,7 @@ impl DataModelExt for PaymentAttempt { profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, card_discovery: storage_model.card_discovery, + charges: storage_model.charges, } } } @@ -1664,7 +1667,6 @@ impl DataModelExt for PaymentAttemptNew { mandate_data: self.mandate_data.map(|d| d.to_storage_model()), payment_method_billing_address_id: self.payment_method_billing_address_id, fingerprint_id: self.fingerprint_id, - charge_id: self.charge_id, client_source: self.client_source, client_version: self.client_version, customer_acceptance: self.customer_acceptance, @@ -1739,7 +1741,6 @@ impl DataModelExt for PaymentAttemptNew { .map(MandateDetails::from_storage_model), payment_method_billing_address_id: storage_model.payment_method_billing_address_id, fingerprint_id: storage_model.fingerprint_id, - charge_id: storage_model.charge_id, client_source: storage_model.client_source, client_version: storage_model.client_version, customer_acceptance: storage_model.customer_acceptance, diff --git a/migrations/2025-01-14-832737_add_charges_to_payment_attempt/down.sql b/migrations/2025-01-14-832737_add_charges_to_payment_attempt/down.sql new file mode 100644 index 00000000000..cdd8328c8ee --- /dev/null +++ b/migrations/2025-01-14-832737_add_charges_to_payment_attempt/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_attempt +DROP COLUMN charges; \ No newline at end of file diff --git a/migrations/2025-01-14-832737_add_charges_to_payment_attempt/up.sql b/migrations/2025-01-14-832737_add_charges_to_payment_attempt/up.sql new file mode 100644 index 00000000000..10fa978f9dc --- /dev/null +++ b/migrations/2025-01-14-832737_add_charges_to_payment_attempt/up.sql @@ -0,0 +1,3 @@ +ALTER TABLE payment_attempt +ADD COLUMN charges JSONB +DEFAULT NULL; \ No newline at end of file diff --git a/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql b/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql index 2dcd3a16a6c..f67b9b2e7ac 100644 --- a/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql @@ -89,7 +89,8 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN confirm BOOLEAN, ADD COLUMN authentication_data JSONB, ADD COLUMN payment_method_billing_address_id VARCHAR(64), - ADD COLUMN connector_mandate_detail JSONB; + ADD COLUMN connector_mandate_detail JSONB, + ADD COLUMN charge_id VARCHAR(64); -- Create the index which was dropped because of dropping the column CREATE INDEX payment_attempt_connector_transaction_id_merchant_id_index ON payment_attempt (connector_transaction_id, merchant_id); diff --git a/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql b/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql index 65eb29c474f..e19f8631f62 100644 --- a/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql @@ -87,4 +87,5 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN confirm, DROP COLUMN authentication_data, DROP COLUMN payment_method_billing_address_id, - DROP COLUMN connector_mandate_detail; + DROP COLUMN connector_mandate_detail, + DROP COLUMN charge_id; From 7b015c5de061f6d6794dfcf5c7711809d325f46b Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Mon, 10 Feb 2025 13:09:42 +0530 Subject: [PATCH 072/133] feat(connector): [Datatrans] Add Wasm Changes (#7229) --- .../connector_configs/toml/development.toml | 18 ++++++++++++++++++ crates/connector_configs/toml/production.toml | 19 +++++++++++++++++++ crates/connector_configs/toml/sandbox.toml | 19 +++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 45ae3ceea61..1aa77e61edb 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -4177,6 +4177,24 @@ key1="Public Api Key" [datatrans.connector_auth.BodyKey] api_key = "Passcode" key1 = "datatrans MerchantId" +[datatrans.metadata.acquirer_bin] +name="acquirer_bin" +label="Acquirer Bin" +placeholder="Enter Acquirer Bin" +required=false +type="Text" +[datatrans.metadata.acquirer_merchant_id] +name="acquirer_merchant_id" +label="Acquirer Merchant ID" +placeholder="Enter Acquirer Merchant ID" +required=false +type="Text" +[datatrans.metadata.acquirer_country_code] +name="acquirer_country_code" +label="Acquirer Country Code" +placeholder="Enter Acquirer Country Code" +required=false +type="Text" [paybox] [[paybox.credit]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 104121de0e1..e5fd8c86f8b 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -3149,6 +3149,25 @@ key2 ="Merchant Id" api_key = "Passcode" key1 = "datatrans MerchantId" +[datatrans.metadata.acquirer_bin] +name="acquirer_bin" +label="Acquirer Bin" +placeholder="Enter Acquirer Bin" +required=false +type="Text" +[datatrans.metadata.acquirer_merchant_id] +name="acquirer_merchant_id" +label="Acquirer Merchant ID" +placeholder="Enter Acquirer Merchant ID" +required=false +type="Text" +[datatrans.metadata.acquirer_country_code] +name="acquirer_country_code" +label="Acquirer Country Code" +placeholder="Enter Acquirer Country Code" +required=false +type="Text" + [wellsfargo] [[wellsfargo.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index decfcf90b95..c82c316d910 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -4114,6 +4114,25 @@ key1="Public Api Key" [datatrans.connector_auth.BodyKey] api_key = "Passcode" key1 = "datatrans MerchantId" +[datatrans.metadata.acquirer_bin] +name="acquirer_bin" +label="Acquirer Bin" +placeholder="Enter Acquirer Bin" +required=false +type="Text" +[datatrans.metadata.acquirer_merchant_id] +name="acquirer_merchant_id" +label="Acquirer Merchant ID" +placeholder="Enter Acquirer Merchant ID" +required=false +type="Text" +[datatrans.metadata.acquirer_country_code] +name="acquirer_country_code" +label="Acquirer Country Code" +placeholder="Enter Acquirer Country Code" +required=false +type="Text" + [paybox] [[paybox.credit]] From 647e163117a564f4be56b7b6a31b13007d3066f0 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Mon, 10 Feb 2025 16:51:07 +0530 Subject: [PATCH 073/133] refactor(router): add feature_metadata for merchant_connector_account create v2 flow (#7144) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 70 ++++++++++ crates/api_models/src/admin.rs | 34 +++++ .../src/merchant_connector_account.rs | 37 +++++- crates/diesel_models/src/schema_v2.rs | 1 + .../src/merchant_connector_account.rs | 125 +++++++++++++++++- crates/openapi/src/openapi_v2.rs | 2 + crates/router/src/core/admin.rs | 27 +++- .../src/core/connector_onboarding/paypal.rs | 1 + crates/router/src/core/verification/utils.rs | 3 +- .../src/db/merchant_connector_account.rs | 2 + crates/router/src/types.rs | 55 ++++++++ crates/router/src/types/transformers.rs | 5 + .../down.sql | 2 + .../up.sql | 2 + 14 files changed, 356 insertions(+), 10 deletions(-) create mode 100644 v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/down.sql create mode 100644 v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/up.sql diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 7cdd6fe9d31..f7f1ca34a3f 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -10908,6 +10908,20 @@ }, "additionalProperties": false }, + "MerchantConnectorAccountFeatureMetadata": { + "type": "object", + "description": "Feature metadata for merchant connector account", + "properties": { + "revenue_recovery": { + "allOf": [ + { + "$ref": "#/components/schemas/RevenueRecoveryMetadata" + } + ], + "nullable": true + } + } + }, "MerchantConnectorCreate": { "type": "object", "description": "Create a new Merchant Connector for the merchant account. The connector could be a payment processor / facilitator / acquirer or specialized services like Fraud / Accounting etc.\"", @@ -11003,6 +11017,14 @@ } ], "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/MerchantConnectorAccountFeatureMetadata" + } + ], + "nullable": true } }, "additionalProperties": false @@ -11263,6 +11285,14 @@ } ], "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/MerchantConnectorAccountFeatureMetadata" + } + ], + "nullable": true } }, "additionalProperties": false @@ -11360,6 +11390,14 @@ } ], "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/MerchantConnectorAccountFeatureMetadata" + } + ], + "nullable": true } }, "additionalProperties": false @@ -18725,6 +18763,38 @@ "requeue" ] }, + "RevenueRecoveryMetadata": { + "type": "object", + "description": "Revenue recovery metadata for merchant connector account", + "required": [ + "max_retry_count", + "billing_connector_retry_threshold", + "billing_account_reference" + ], + "properties": { + "max_retry_count": { + "type": "integer", + "format": "int32", + "description": "The maximum number of retries allowed for an invoice. This limit is set by the merchant for each `billing connector`. Once this limit is reached, no further retries will be attempted.", + "example": "15", + "minimum": 0 + }, + "billing_connector_retry_threshold": { + "type": "integer", + "format": "int32", + "description": "Maximum number of `billing connector` retries before revenue recovery can start executing retries.", + "example": "10", + "minimum": 0 + }, + "billing_account_reference": { + "type": "integer", + "format": "int32", + "description": "Billing account reference id is payment gateway id at billing connector end.\nMerchants need to provide a mapping between these merchant connector account and the corresponding account reference IDs for each `billing connector`.", + "example": "{ \"mca_vDSg5z6AxnisHq5dbJ6g\": \"stripe_123\", \"mca_vDSg5z6AumisHqh4x5m1\": \"adyen_123\" }", + "minimum": 0 + } + } + }, "RevokeApiKeyResponse": { "type": "object", "description": "The response body for revoking an API Key.", diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index e47f7826d59..c1312433947 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -732,6 +732,10 @@ pub struct MerchantConnectorCreate { /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials #[schema(value_type = Option)] pub connector_wallets_details: Option, + + /// Additional data that might be required by hyperswitch, to enable some specific features. + #[schema(value_type = Option)] + pub feature_metadata: Option, } #[cfg(feature = "v2")] @@ -906,6 +910,28 @@ pub enum AdditionalMerchantData { OpenBankingRecipientData(MerchantRecipientData), } +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +/// Feature metadata for merchant connector account +pub struct MerchantConnectorAccountFeatureMetadata { + /// Revenue recovery metadata for merchant connector account + pub revenue_recovery: Option, +} + +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +/// Revenue recovery metadata for merchant connector account +pub struct RevenueRecoveryMetadata { + /// The maximum number of retries allowed for an invoice. This limit is set by the merchant for each `billing connector`. Once this limit is reached, no further retries will be attempted. + #[schema(value_type = u16, example = "15")] + pub max_retry_count: u16, + /// Maximum number of `billing connector` retries before revenue recovery can start executing retries. + #[schema(value_type = u16, example = "10")] + pub billing_connector_retry_threshold: u16, + /// Billing account reference id is payment gateway id at billing connector end. + /// Merchants need to provide a mapping between these merchant connector account and the corresponding account reference IDs for each `billing connector`. + #[schema(value_type = u16, example = r#"{ "mca_vDSg5z6AxnisHq5dbJ6g": "stripe_123", "mca_vDSg5z6AumisHqh4x5m1": "adyen_123" }"#)] + pub billing_account_reference: HashMap, +} + #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] #[serde(rename_all = "snake_case")] pub enum MerchantAccountData { @@ -1070,6 +1096,10 @@ pub struct MerchantConnectorResponse { /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials #[schema(value_type = Option)] pub connector_wallets_details: Option, + + /// Additional data that might be required by hyperswitch, to enable some specific features. + #[schema(value_type = Option)] + pub feature_metadata: Option, } #[cfg(feature = "v2")] @@ -1532,6 +1562,10 @@ pub struct MerchantConnectorUpdate { /// The connector_wallets_details is used to store wallet details such as certificates and wallet credentials pub connector_wallets_details: Option, + + /// Additional data that might be required by hyperswitch, to enable some specific features. + #[schema(value_type = Option)] + pub feature_metadata: Option, } #[cfg(feature = "v2")] diff --git a/crates/diesel_models/src/merchant_connector_account.rs b/crates/diesel_models/src/merchant_connector_account.rs index e536648a6d7..01853cb7aa6 100644 --- a/crates/diesel_models/src/merchant_connector_account.rs +++ b/crates/diesel_models/src/merchant_connector_account.rs @@ -1,13 +1,17 @@ +#[cfg(feature = "v2")] +use std::collections::HashMap; use std::fmt::Debug; use common_utils::{encryption::Encryption, id_type, pii}; +#[cfg(feature = "v2")] +use diesel::{sql_types::Jsonb, AsExpression}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use crate::enums as storage_enums; #[cfg(feature = "v1")] use crate::schema::merchant_connector_account; #[cfg(feature = "v2")] -use crate::schema_v2::merchant_connector_account; +use crate::{enums, schema_v2::merchant_connector_account, types}; #[cfg(feature = "v1")] #[derive( @@ -95,6 +99,7 @@ pub struct MerchantConnectorAccount { pub connector_wallets_details: Option, pub version: common_enums::ApiVersion, pub id: id_type::MerchantConnectorAccountId, + pub feature_metadata: Option, } #[cfg(feature = "v2")] @@ -164,6 +169,7 @@ pub struct MerchantConnectorAccountNew { pub connector_wallets_details: Option, pub id: id_type::MerchantConnectorAccountId, pub version: common_enums::ApiVersion, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -213,6 +219,7 @@ pub struct MerchantConnectorAccountUpdateInternal { pub status: Option, pub connector_wallets_details: Option, pub additional_merchant_data: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -260,8 +267,36 @@ impl MerchantConnectorAccountUpdateInternal { modified_at: self.modified_at.unwrap_or(source.modified_at), pm_auth_config: self.pm_auth_config, status: self.status.unwrap_or(source.status), + feature_metadata: self.feature_metadata, ..source } } } + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, AsExpression)] +#[diesel(sql_type = Jsonb)] +pub struct MerchantConnectorAccountFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +common_utils::impl_to_sql_from_sql_json!(MerchantConnectorAccountFeatureMetadata); + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct RevenueRecoveryMetadata { + /// The maximum number of retries allowed for an invoice. This limit is set by the merchant for each `billing connector`.Once this limit is reached, no further retries will be attempted. + pub max_retry_count: u16, + /// Maximum number of `billing connector` retries before revenue recovery can start executing retries. + pub billing_connector_retry_threshold: u16, + /// Billing account reference id is payment gateway id at billing connector end. + /// Merchants need to provide a mapping between these merchant connector account and the corresponding + /// account reference IDs for each `billing connector`. + pub billing_account_reference: BillingAccountReference, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct BillingAccountReference(pub HashMap); diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index 59a265e25f5..d17d4f8478a 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -763,6 +763,7 @@ diesel::table! { version -> ApiVersion, #[max_length = 64] id -> Varchar, + feature_metadata -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs index cd85d56b72f..9208bb486f7 100644 --- a/crates/hyperswitch_domain_models/src/merchant_connector_account.rs +++ b/crates/hyperswitch_domain_models/src/merchant_connector_account.rs @@ -1,3 +1,8 @@ +#[cfg(feature = "v2")] +use std::collections::HashMap; + +#[cfg(feature = "v2")] +use common_utils::transformers::ForeignTryFrom; use common_utils::{ crypto::Encryptable, date_time, @@ -7,6 +12,12 @@ use common_utils::{ id_type, pii, type_name, types::keymanager::{Identifier, KeyManagerState, ToEncryptable}, }; +#[cfg(feature = "v2")] +use diesel_models::merchant_connector_account::{ + BillingAccountReference as DieselBillingAccountReference, + MerchantConnectorAccountFeatureMetadata as DieselMerchantConnectorAccountFeatureMetadata, + RevenueRecoveryMetadata as DieselRevenueRecoveryMetadata, +}; use diesel_models::{enums, merchant_connector_account::MerchantConnectorAccountUpdateInternal}; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; @@ -14,6 +25,8 @@ use rustc_hash::FxHashMap; use serde_json::Value; use super::behaviour; +#[cfg(feature = "v2")] +use crate::errors::{self, api_error_response}; use crate::{ mandates::CommonMandateReference, router_data, @@ -102,6 +115,7 @@ pub struct MerchantConnectorAccount { #[encrypt] pub additional_merchant_data: Option>>, pub version: common_enums::ApiVersion, + pub feature_metadata: Option, } #[cfg(feature = "v2")] @@ -149,6 +163,63 @@ pub struct PaymentMethodsEnabledForConnector { pub connector: common_enums::connector_enums::Connector, } +#[cfg(feature = "v2")] +#[derive(Debug, Clone)] +pub struct MerchantConnectorAccountFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone)] +pub struct RevenueRecoveryMetadata { + pub max_retry_count: u16, + pub billing_connector_retry_threshold: u16, + pub mca_reference: AccountReferenceMap, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone)] +pub struct AccountReferenceMap { + pub recovery_to_billing: HashMap, + pub billing_to_recovery: HashMap, +} + +#[cfg(feature = "v2")] +impl AccountReferenceMap { + pub fn new( + hash_map: HashMap, + ) -> Result { + Self::validate(&hash_map)?; + + let recovery_to_billing = hash_map.clone(); + let mut billing_to_recovery = HashMap::new(); + + for (key, value) in &hash_map { + billing_to_recovery.insert(value.clone(), key.clone()); + } + + Ok(Self { + recovery_to_billing, + billing_to_recovery, + }) + } + + fn validate( + hash_map: &HashMap, + ) -> Result<(), api_error_response::ApiErrorResponse> { + let mut seen_values = std::collections::HashSet::new(); // To check uniqueness of values + + for value in hash_map.values() { + if !seen_values.insert(value.clone()) { + return Err(api_error_response::ApiErrorResponse::InvalidRequestData { + message: "Duplicate account reference IDs found in Recovery feature metadata. Each account reference ID must be unique.".to_string(), + }); + } + } + Ok(()) + } +} + #[cfg(feature = "v2")] /// Holds the payment methods enabled for a connector pub struct FlattenedPaymentMethodsEnabled { @@ -234,13 +305,14 @@ pub enum MerchantConnectorAccountUpdate { payment_methods_enabled: Option>, metadata: Option, frm_configs: Option>, - connector_webhook_details: Option, + connector_webhook_details: Box>, applepay_verified_domains: Option>, pm_auth_config: Box>, connector_label: Option, status: Option, connector_wallets_details: Box>>, additional_merchant_data: Box>>, + feature_metadata: Box>, }, ConnectorWalletDetailsUpdate { connector_wallets_details: Encryptable, @@ -410,6 +482,7 @@ impl behaviour::Conversion for MerchantConnectorAccount { connector_wallets_details: self.connector_wallets_details.map(Encryption::from), additional_merchant_data: self.additional_merchant_data.map(|data| data.into()), version: self.version, + feature_metadata: self.feature_metadata.map(From::from), }, ) } @@ -468,6 +541,7 @@ impl behaviour::Conversion for MerchantConnectorAccount { connector_wallets_details: decrypted_data.connector_wallets_details, additional_merchant_data: decrypted_data.additional_merchant_data, version: other.version, + feature_metadata: other.feature_metadata.map(From::from), }) } @@ -494,6 +568,7 @@ impl behaviour::Conversion for MerchantConnectorAccount { connector_wallets_details: self.connector_wallets_details.map(Encryption::from), additional_merchant_data: self.additional_merchant_data.map(|data| data.into()), version: self.version, + feature_metadata: self.feature_metadata.map(From::from), }) } } @@ -583,6 +658,7 @@ impl From for MerchantConnectorAccountUpdateInte status, connector_wallets_details, additional_merchant_data, + feature_metadata, } => Self { connector_type, connector_account_details: connector_account_details.map(Encryption::from), @@ -591,13 +667,14 @@ impl From for MerchantConnectorAccountUpdateInte metadata, frm_config: frm_configs, modified_at: Some(date_time::now()), - connector_webhook_details, + connector_webhook_details: *connector_webhook_details, applepay_verified_domains, pm_auth_config: *pm_auth_config, connector_label, status, connector_wallets_details: connector_wallets_details.map(Encryption::from), additional_merchant_data: additional_merchant_data.map(Encryption::from), + feature_metadata: feature_metadata.map(From::from), }, MerchantConnectorAccountUpdate::ConnectorWalletDetailsUpdate { connector_wallets_details, @@ -616,6 +693,7 @@ impl From for MerchantConnectorAccountUpdateInte pm_auth_config: None, status: None, additional_merchant_data: None, + feature_metadata: None, }, } } @@ -680,3 +758,46 @@ common_utils::create_list_wrapper!( } } ); + +#[cfg(feature = "v2")] +impl From + for DieselMerchantConnectorAccountFeatureMetadata +{ + fn from(feature_metadata: MerchantConnectorAccountFeatureMetadata) -> Self { + let revenue_recovery = feature_metadata.revenue_recovery.map(|recovery_metadata| { + DieselRevenueRecoveryMetadata { + max_retry_count: recovery_metadata.max_retry_count, + billing_connector_retry_threshold: recovery_metadata + .billing_connector_retry_threshold, + billing_account_reference: DieselBillingAccountReference( + recovery_metadata.mca_reference.recovery_to_billing, + ), + } + }); + Self { revenue_recovery } + } +} + +#[cfg(feature = "v2")] +impl From + for MerchantConnectorAccountFeatureMetadata +{ + fn from(feature_metadata: DieselMerchantConnectorAccountFeatureMetadata) -> Self { + let revenue_recovery = feature_metadata.revenue_recovery.map(|recovery_metadata| { + let mut billing_to_recovery = HashMap::new(); + for (key, value) in &recovery_metadata.billing_account_reference.0 { + billing_to_recovery.insert(value.to_string(), key.clone()); + } + RevenueRecoveryMetadata { + max_retry_count: recovery_metadata.max_retry_count, + billing_connector_retry_threshold: recovery_metadata + .billing_connector_retry_threshold, + mca_reference: AccountReferenceMap { + recovery_to_billing: recovery_metadata.billing_account_reference.0, + billing_to_recovery, + }, + } + }); + Self { revenue_recovery } + } +} diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 0da00cbfd88..f2b692c27a2 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -188,6 +188,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::admin::BusinessGenericLinkConfig, api_models::admin::BusinessCollectLinkConfig, api_models::admin::BusinessPayoutLinkConfig, + api_models::admin::MerchantConnectorAccountFeatureMetadata, + api_models::admin::RevenueRecoveryMetadata, api_models::customers::CustomerRequest, api_models::customers::CustomerUpdateRequest, api_models::customers::CustomerDeleteResponse, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 7b4cbb282df..a75d16a7300 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -2186,6 +2186,12 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details")?; + let feature_metadata = self + .feature_metadata + .as_ref() + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; + Ok(storage::MerchantConnectorAccountUpdate::Update { connector_type: Some(self.connector_type), connector_label: self.connector_label.clone(), @@ -2195,18 +2201,21 @@ impl MerchantConnectorAccountUpdateBridge for api_models::admin::MerchantConnect metadata: self.metadata, frm_configs, connector_webhook_details: match &self.connector_webhook_details { - Some(connector_webhook_details) => connector_webhook_details - .encode_to_value() - .change_context(errors::ApiErrorResponse::InternalServerError) - .map(Some)? - .map(Secret::new), - None => None, + Some(connector_webhook_details) => Box::new( + connector_webhook_details + .encode_to_value() + .change_context(errors::ApiErrorResponse::InternalServerError) + .map(Some)? + .map(Secret::new), + ), + None => Box::new(None), }, applepay_verified_domains: None, pm_auth_config: Box::new(self.pm_auth_config), status: Some(connector_status), additional_merchant_data: Box::new(encrypted_data.additional_merchant_data), connector_wallets_details: Box::new(encrypted_data.connector_wallets_details), + feature_metadata: Box::new(feature_metadata), }) } } @@ -2503,6 +2512,11 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed while decrypting connector account details")?; + let feature_metadata = self + .feature_metadata + .as_ref() + .map(ForeignTryFrom::foreign_try_from) + .transpose()?; Ok(domain::MerchantConnectorAccount { merchant_id: business_profile.merchant_id.clone(), connector_type: self.connector_type, @@ -2534,6 +2548,7 @@ impl MerchantConnectorAccountCreateBridge for api::MerchantConnectorCreate { connector_wallets_details: encrypted_data.connector_wallets_details, additional_merchant_data: encrypted_data.additional_merchant_data, version: hyperswitch_domain_models::consts::API_VERSION, + feature_metadata, }) } diff --git a/crates/router/src/core/connector_onboarding/paypal.rs b/crates/router/src/core/connector_onboarding/paypal.rs index 88e1eb80618..98aef1e3064 100644 --- a/crates/router/src/core/connector_onboarding/paypal.rs +++ b/crates/router/src/core/connector_onboarding/paypal.rs @@ -179,6 +179,7 @@ pub async fn update_mca( merchant_id: merchant_id.clone(), additional_merchant_data: None, connector_wallets_details: None, + feature_metadata: None, }; let mca_response = admin::update_connector(state.clone(), &merchant_id, None, &connector_id, request).await?; diff --git a/crates/router/src/core/verification/utils.rs b/crates/router/src/core/verification/utils.rs index a51b6c86a34..4967f08bcb8 100644 --- a/crates/router/src/core/verification/utils.rs +++ b/crates/router/src/core/verification/utils.rs @@ -91,13 +91,14 @@ pub async fn check_existence_and_add_domain_to_db( payment_methods_enabled: None, metadata: None, frm_configs: None, - connector_webhook_details: None, + connector_webhook_details: Box::new(None), applepay_verified_domains: Some(already_verified_domains.clone()), pm_auth_config: Box::new(None), connector_label: None, status: None, connector_wallets_details: Box::new(None), additional_merchant_data: Box::new(None), + feature_metadata: Box::new(None), }; state .store diff --git a/crates/router/src/db/merchant_connector_account.rs b/crates/router/src/db/merchant_connector_account.rs index 34b23a833ac..1175aa43c5d 100644 --- a/crates/router/src/db/merchant_connector_account.rs +++ b/crates/router/src/db/merchant_connector_account.rs @@ -1293,6 +1293,7 @@ impl MerchantConnectorAccountInterface for MockDb { connector_wallets_details: t.connector_wallets_details.map(Encryption::from), additional_merchant_data: t.additional_merchant_data.map(|data| data.into()), version: t.version, + feature_metadata: t.feature_metadata.map(From::from), }; accounts.push(account.clone()); account @@ -1850,6 +1851,7 @@ mod merchant_connector_account_cache_tests { ), additional_merchant_data: None, version: hyperswitch_domain_models::consts::API_VERSION, + feature_metadata: None, }; db.insert_merchant_connector_account(key_manager_state, mca.clone(), &merchant_key) diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 4ad2bd4998e..a5bdef9572f 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -21,7 +21,11 @@ use std::marker::PhantomData; pub use api_models::{enums::Connector, mandates}; #[cfg(feature = "payouts")] pub use api_models::{enums::PayoutConnectors, payouts as payout_types}; +#[cfg(feature = "v2")] +use common_utils::errors::CustomResult; pub use common_utils::{pii, pii::Email, request::RequestContent, types::MinorUnit}; +#[cfg(feature = "v2")] +use error_stack::ResultExt; #[cfg(feature = "frm")] pub use hyperswitch_domain_models::router_data_v2::FrmFlowData; use hyperswitch_domain_models::router_flow_types::{ @@ -1031,3 +1035,54 @@ impl } } } + +#[cfg(feature = "v2")] +impl ForeignFrom<&domain::MerchantConnectorAccountFeatureMetadata> + for api_models::admin::MerchantConnectorAccountFeatureMetadata +{ + fn foreign_from(item: &domain::MerchantConnectorAccountFeatureMetadata) -> Self { + let revenue_recovery = item + .revenue_recovery + .as_ref() + .map( + |revenue_recovery_metadata| api_models::admin::RevenueRecoveryMetadata { + max_retry_count: revenue_recovery_metadata.max_retry_count, + billing_connector_retry_threshold: revenue_recovery_metadata + .billing_connector_retry_threshold, + billing_account_reference: revenue_recovery_metadata + .mca_reference + .recovery_to_billing + .clone(), + }, + ); + Self { revenue_recovery } + } +} + +#[cfg(feature = "v2")] +impl ForeignTryFrom<&api_models::admin::MerchantConnectorAccountFeatureMetadata> + for domain::MerchantConnectorAccountFeatureMetadata +{ + type Error = errors::ApiErrorResponse; + fn foreign_try_from( + feature_metadata: &api_models::admin::MerchantConnectorAccountFeatureMetadata, + ) -> Result { + let revenue_recovery = feature_metadata + .revenue_recovery + .as_ref() + .map(|revenue_recovery_metadata| { + domain::AccountReferenceMap::new( + revenue_recovery_metadata.billing_account_reference.clone(), + ) + .map(|mca_reference| domain::RevenueRecoveryMetadata { + max_retry_count: revenue_recovery_metadata.max_retry_count, + billing_connector_retry_threshold: revenue_recovery_metadata + .billing_connector_retry_threshold, + mca_reference, + }) + }) + .transpose()?; + + Ok(Self { revenue_recovery }) + } +} diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 3e81d939c14..a945f8ebfb4 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -1293,6 +1293,10 @@ impl ForeignTryFrom .attach_printable("Failed to encode ConnectorAuthType")?, ); + let feature_metadata = item.feature_metadata.as_ref().map(|metadata| { + api_models::admin::MerchantConnectorAccountFeatureMetadata::foreign_from(metadata) + }); + let response = Self { id: item.get_id(), connector_type: item.connector_type, @@ -1343,6 +1347,7 @@ impl ForeignTryFrom .change_context(errors::ApiErrorResponse::InternalServerError) }) .transpose()?, + feature_metadata, }; Ok(response) } diff --git a/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/down.sql b/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/down.sql new file mode 100644 index 00000000000..2e343c15ada --- /dev/null +++ b/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE merchant_connector_account +DROP COLUMN IF EXISTS feature_metadata; \ No newline at end of file diff --git a/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/up.sql b/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/up.sql new file mode 100644 index 00000000000..24ceb8bd423 --- /dev/null +++ b/v2_migrations/2025-01-29-103844_add_feature_metadata_in_merchant_connector_account/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE merchant_connector_account +ADD COLUMN feature_metadata JSONB; \ No newline at end of file From 76c34595ef612ca1a3b750653e6460b980163d63 Mon Sep 17 00:00:00 2001 From: Anurag Date: Mon, 10 Feb 2025 17:06:21 +0530 Subject: [PATCH 074/133] fix(connector): fix incorrect mapping of attempt status in NMI connector (#7200) Co-authored-by: Anurag Singh --- crates/router/src/connector/nmi/transformers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index e99549893b6..e70813216d2 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -910,7 +910,7 @@ impl TryFrom> { enums::AttemptStatus::CaptureInitiated } else { - enums::AttemptStatus::Authorizing + enums::AttemptStatus::Authorized }, ), Response::Declined | Response::Error => ( From bfcaf003427caf9580a2520b3f2efc8773818905 Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Mon, 10 Feb 2025 17:07:07 +0530 Subject: [PATCH 075/133] refactor(connector): [Authorizedotnet] fix refund status mapping (#7208) --- crates/router/src/connector/authorizedotnet/transformers.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/router/src/connector/authorizedotnet/transformers.rs b/crates/router/src/connector/authorizedotnet/transformers.rs index 3be29293972..d48ead5d52e 100644 --- a/crates/router/src/connector/authorizedotnet/transformers.rs +++ b/crates/router/src/connector/authorizedotnet/transformers.rs @@ -1361,11 +1361,12 @@ impl TryFrom<&AuthorizedotnetRouterData<&types::RefundsRouterData>> for Cr impl From for enums::RefundStatus { fn from(item: AuthorizedotnetRefundStatus) -> Self { match item { - AuthorizedotnetRefundStatus::Approved => Self::Success, AuthorizedotnetRefundStatus::Declined | AuthorizedotnetRefundStatus::Error => { Self::Failure } - AuthorizedotnetRefundStatus::HeldForReview => Self::Pending, + AuthorizedotnetRefundStatus::Approved | AuthorizedotnetRefundStatus::HeldForReview => { + Self::Pending + } } } } From b616bd130b19b259a3b05377bdbae37834f8647d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 11 Feb 2025 00:27:03 +0000 Subject: [PATCH 076/133] chore(version): 2025.02.11.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f8bc2313ce..ed74329eef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,28 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.11.0 + +### Features + +- **connector:** [Datatrans] Add Wasm Changes ([#7229](https://github.com/juspay/hyperswitch/pull/7229)) ([`7b015c5`](https://github.com/juspay/hyperswitch/commit/7b015c5de061f6d6794dfcf5c7711809d325f46b)) +- **router:** Add adyen split payments support ([#6952](https://github.com/juspay/hyperswitch/pull/6952)) ([`323d763`](https://github.com/juspay/hyperswitch/commit/323d763087fd7453f05153b97d6b53e211cf74ba)) + +### Bug Fixes + +- **connector:** + - [fiuu] update PSync and webhooks response ([#7211](https://github.com/juspay/hyperswitch/pull/7211)) ([`1c54211`](https://github.com/juspay/hyperswitch/commit/1c54211b2f8aa650fc4dbb7ab3d796e21d50461a)) + - Fix incorrect mapping of attempt status in NMI connector ([#7200](https://github.com/juspay/hyperswitch/pull/7200)) ([`76c3459`](https://github.com/juspay/hyperswitch/commit/76c34595ef612ca1a3b750653e6460b980163d63)) + +### Refactors + +- **connector:** [Authorizedotnet] fix refund status mapping ([#7208](https://github.com/juspay/hyperswitch/pull/7208)) ([`bfcaf00`](https://github.com/juspay/hyperswitch/commit/bfcaf003427caf9580a2520b3f2efc8773818905)) +- **router:** Add feature_metadata for merchant_connector_account create v2 flow ([#7144](https://github.com/juspay/hyperswitch/pull/7144)) ([`647e163`](https://github.com/juspay/hyperswitch/commit/647e163117a564f4be56b7b6a31b13007d3066f0)) + +**Full Changelog:** [`2025.02.10.0...2025.02.11.0`](https://github.com/juspay/hyperswitch/compare/2025.02.10.0...2025.02.11.0) + +- - - + ## 2025.02.10.0 ### Features From 1d607d7970abe204bc6101a81ba26652eadcbd04 Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:12:49 +0530 Subject: [PATCH 077/133] fix(payments): [Payment links] Add fix for payment link redirection url (#7232) --- .../payment_link_initiator.js | 18 ++++++++++--- .../secure_payment_link_initiator.js | 25 +++++++++++++------ 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index bc851bbeb19..2d2b2c30ae2 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -32,7 +32,7 @@ function initializeSDK() { redirectionFlags: { shouldRemoveBeforeUnloadEvents: true, shouldUseTopRedirection: true, - } + }, }); // @ts-ignore widgets = hyper.widgets({ @@ -86,9 +86,19 @@ function initializeSDK() { function redirectToStatus() { var paymentDetails = window.__PAYMENT_DETAILS; var arr = window.location.pathname.split("/"); - arr.splice(0, 2); - arr.unshift("status"); - arr.unshift("payment_link"); + + // NOTE - This code preserves '/api' in url for integ and sbx + // e.g. url for integ/sbx - https://integ.hyperswitch.io/api/payment_link/merchant_1234/pay_1234?locale=en + // e.g. url for others - https://abc.dev.com/payment_link/merchant_1234/pay_1234?locale=en + var hasApiInPath = arr.includes("api"); + if (hasApiInPath) { + arr.splice(0, 3); + arr.unshift("api", "payment_link", "status"); + } else { + arr.splice(0, 2); + arr.unshift("payment_link", "status"); + } + window.location.href = window.location.origin + "/" + diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 290df7d1203..9ae64ec3133 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -53,7 +53,7 @@ if (!isFramed) { redirectionFlags: { shouldRemoveBeforeUnloadEvents: true, shouldUseTopRedirection: true, - } + }, }); // @ts-ignore widgets = hyper.widgets({ @@ -63,7 +63,7 @@ if (!isFramed) { }); var type = paymentDetails.sdk_layout === "spaced_accordion" || - paymentDetails.sdk_layout === "accordion" + paymentDetails.sdk_layout === "accordion" ? "accordion" : paymentDetails.sdk_layout; @@ -109,9 +109,19 @@ if (!isFramed) { function redirectToStatus() { var paymentDetails = window.__PAYMENT_DETAILS; var arr = window.location.pathname.split("/"); - arr.splice(0, 3); - arr.unshift("status"); - arr.unshift("payment_link"); + + // NOTE - This code preserves '/api' in url for integ and sbx envs + // e.g. url for integ/sbx - https://integ.hyperswitch.io/api/payment_link/s/merchant_1234/pay_1234?locale=en + // e.g. url for others - https://abc.dev.com/payment_link/s/merchant_1234/pay_1234?locale=en + var hasApiInPath = arr.includes("api"); + if (hasApiInPath) { + arr.splice(0, 4); + arr.unshift("api", "payment_link", "status"); + } else { + arr.splice(0, 3); + arr.unshift("payment_link", "status"); + } + let returnUrl = window.location.origin + "/" + @@ -127,9 +137,10 @@ if (!isFramed) { var { paymentId, merchantId, attemptId, connector } = parseRoute(url); var urlToPost = getEnvRoute(url); var message = { - message: "CRITICAL ERROR - Failed to redirect top document. Falling back to redirecting using window.location", + message: + "CRITICAL ERROR - Failed to redirect top document. Falling back to redirecting using window.location", reason: error.message, - } + }; var log = { message, url, From d09331701997b70672d4d768e8139c12fffb7ad1 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:13:51 +0530 Subject: [PATCH 078/133] refactor(core): add support for expand attempt list in psync v2 (#7209) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 161 +++++++++++------- crates/api_models/src/payments.rs | 107 +++++++++++- .../src/query/payment_attempt.rs | 15 ++ .../hyperswitch_domain_models/src/payments.rs | 1 + .../src/payments/payment_attempt.rs | 9 + crates/openapi/src/openapi_v2.rs | 1 + crates/router/src/core/payments.rs | 1 + .../core/payments/operations/payment_get.rs | 21 +++ .../router/src/core/payments/transformers.rs | 62 +++++++ .../router/src/core/webhooks/incoming_v2.rs | 2 + crates/router/src/db/kafka_store.rs | 18 ++ .../src/mock_db/payment_attempt.rs | 12 ++ .../src/payments/payment_attempt.rs | 56 ++++++ .../down.sql | 3 + .../up.sql | 3 + 15 files changed, 407 insertions(+), 65 deletions(-) create mode 100644 v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/down.sql create mode 100644 v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/up.sql diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index f7f1ca34a3f..96294f56a96 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -12815,71 +12815,92 @@ } } }, - "PaymentAttemptResponse": { + "PaymentAttemptAmountDetails": { "type": "object", "required": [ - "attempt_id", - "status", - "amount", - "created_at", - "modified_at" + "net_amount", + "amount_capturable" ], "properties": { - "attempt_id": { - "type": "string", - "description": "Unique identifier for the attempt" - }, - "status": { - "$ref": "#/components/schemas/AttemptStatus" - }, - "amount": { - "type": "integer", - "format": "int64", - "description": "The payment attempt amount. Amount for the payment in lowest denomination of the currency. (i.e) in cents for USD denomination, in paisa for INR denomination etc.,", - "example": 6540 + "net_amount": { + "$ref": "#/components/schemas/MinorUnit" }, - "order_tax_amount": { - "type": "integer", - "format": "int64", - "description": "The payment attempt tax_amount.", - "example": 6540, + "amount_to_capture": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], "nullable": true }, - "currency": { + "surcharge_amount": { "allOf": [ { - "$ref": "#/components/schemas/Currency" + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true }, - "connector": { - "type": "string", - "description": "The connector used for the payment", + "tax_on_surcharge": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], "nullable": true }, - "error_message": { - "type": "string", - "description": "If there was an error while calling the connector, the error message is received here", + "amount_capturable": { + "$ref": "#/components/schemas/MinorUnit" + }, + "shipping_cost": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], "nullable": true }, - "payment_method": { + "order_tax_amount": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethod" + "$ref": "#/components/schemas/MinorUnit" } ], "nullable": true + } + } + }, + "PaymentAttemptResponse": { + "type": "object", + "required": [ + "id", + "status", + "amount", + "authentication_type", + "created_at", + "modified_at" + ], + "properties": { + "id": { + "type": "string", + "description": "The global identifier for the payment attempt" }, - "connector_transaction_id": { + "status": { + "$ref": "#/components/schemas/AttemptStatus" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAttemptAmountDetails" + }, + "connector": { "type": "string", - "description": "A unique identifier for a payment provided by the connector", + "description": "Name of the connector that was used for the payment attempt.", + "example": "stripe", "nullable": true }, - "capture_method": { + "error": { "allOf": [ { - "$ref": "#/components/schemas/CaptureMethod" + "$ref": "#/components/schemas/ErrorDetails" } ], "nullable": true @@ -12890,43 +12911,35 @@ "$ref": "#/components/schemas/AuthenticationType" } ], - "default": "three_ds", - "nullable": true + "default": "three_ds" }, "created_at": { "type": "string", "format": "date-time", - "description": "Time at which the payment attempt was created", - "example": "2022-09-10T10:11:12Z" + "description": "Date and time of Payment attempt creation" }, "modified_at": { "type": "string", "format": "date-time", - "description": "Time at which the payment attempt was last modified", - "example": "2022-09-10T10:11:12Z" + "description": "Time at which the payment attempt was last modified" }, "cancellation_reason": { "type": "string", - "description": "If the payment was cancelled the reason will be provided here", - "nullable": true - }, - "mandate_id": { - "type": "string", - "description": "A unique identifier to link the payment to a mandate, can be use instead of payment_method_data", - "nullable": true - }, - "error_code": { - "type": "string", - "description": "If there was an error while calling the connectors the error code is received here", + "description": "The reason for the cancellation of the payment attempt. Some connectors will have strict rules regarding the values this can have\nCancellation reason will be validated at the connector level when building the request", "nullable": true }, "payment_token": { "type": "string", - "description": "Provide a reference to a stored payment method", + "description": "Payment token is the token used for temporary use in case the payment method is stored in vault", + "example": "187282ab-40ef-47a9-9206-5099ba31e432", "nullable": true }, "connector_metadata": { - "description": "Additional data related to some connectors", + "allOf": [ + { + "$ref": "#/components/schemas/ConnectorMetadata" + } + ], "nullable": true }, "payment_experience": { @@ -12940,25 +12953,35 @@ "payment_method_type": { "allOf": [ { - "$ref": "#/components/schemas/PaymentMethodType" + "$ref": "#/components/schemas/PaymentMethod" } ], "nullable": true }, - "reference_id": { + "connector_reference_id": { "type": "string", - "description": "Reference to the payment at connector side", + "description": "reference(Identifier) to the payment at connector side", "example": "993672945374576J", "nullable": true }, - "unified_code": { + "payment_method_subtype": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + }, + "connector_payment_id": { "type": "string", - "description": "(This field is not live yet)Error code unified across the connectors is received here if there was an error while calling connector", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", "nullable": true }, - "unified_message": { + "payment_method_id": { "type": "string", - "description": "(This field is not live yet)Error message unified across the connectors is received here if there was an error while calling connector", + "description": "Identifier for Payment Method used for the payment attempt", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8", "nullable": true }, "client_source": { @@ -15729,6 +15752,10 @@ "type": "boolean", "description": "A boolean used to indicate if the payment status should be fetched from the connector\nIf this is set to true, the status will be fetched from the connector" }, + "expand_attempts": { + "type": "boolean", + "description": "A boolean used to indicate if all the attempts needs to be fetched for the intent.\nIf this is set to true, attempts list will be available in the response." + }, "param": { "type": "string", "description": "These are the query params that are sent in case of redirect response.\nThese can be ingested by the connector to take necessary actions.", @@ -15856,6 +15883,14 @@ } ], "nullable": true + }, + "attempts": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentAttemptResponse" + }, + "description": "List of payment attempts associated with payment intent", + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index efd000d830d..295b1979f1f 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -656,6 +656,30 @@ pub struct PaymentAmountDetailsResponse { pub amount_captured: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, PartialEq, serde::Serialize, ToSchema)] + +pub struct PaymentAttemptAmountDetails { + /// The total amount of the order including tax, surcharge and shipping cost + pub net_amount: MinorUnit, + /// The amount that was requested to be captured for this payment + pub amount_to_capture: Option, + /// Surcharge amount for the payment attempt. + /// This is either derived by surcharge rules, or sent by the merchant + pub surcharge_amount: Option, + /// Tax amount for the payment attempt + /// This is either derived by surcharge rules, or sent by the merchant + pub tax_on_surcharge: Option, + /// The total amount that can be captured for this payment attempt. + pub amount_capturable: MinorUnit, + /// Shipping cost for the payment attempt. + /// Shipping cost for the payment attempt. + pub shipping_cost: Option, + /// Tax amount for the order. + /// This is either derived by calling an external tax processor, or sent by the merchant + pub order_tax_amount: Option, +} + #[cfg(feature = "v2")] impl AmountDetails { pub fn new(amount_details_setter: AmountDetailsSetter) -> Self { @@ -1313,6 +1337,7 @@ impl RequestSurchargeDetails { } } +#[cfg(feature = "v1")] #[derive(Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema)] pub struct PaymentAttemptResponse { /// Unique identifier for the attempt @@ -1381,6 +1406,78 @@ pub struct PaymentAttemptResponse { pub client_version: Option, } +#[cfg(feature = "v2")] +#[derive(Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema)] +pub struct PaymentAttemptResponse { + /// The global identifier for the payment attempt + #[schema(value_type = String)] + pub id: id_type::GlobalAttemptId, + /// /// The status of the attempt + #[schema(value_type = AttemptStatus, example = "charged")] + pub status: enums::AttemptStatus, + /// Amount related information for this payment and attempt + pub amount: PaymentAttemptAmountDetails, + /// Name of the connector that was used for the payment attempt. + #[schema(example = "stripe")] + pub connector: Option, + + /// Error details for the payment if any + pub error: Option, + + /// The transaction authentication can be set to undergo payer authentication. By default, the authentication will be marked as NO_THREE_DS, as the 3DS method helps with more robust payer authentication + #[schema(value_type = AuthenticationType, example = "no_three_ds", default = "three_ds")] + pub authentication_type: api_enums::AuthenticationType, + + /// Date and time of Payment attempt creation + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created_at: PrimitiveDateTime, + + /// Time at which the payment attempt was last modified + #[serde(with = "common_utils::custom_serde::iso8601")] + pub modified_at: PrimitiveDateTime, + + /// The reason for the cancellation of the payment attempt. Some connectors will have strict rules regarding the values this can have + /// Cancellation reason will be validated at the connector level when building the request + pub cancellation_reason: Option, + + /// Payment token is the token used for temporary use in case the payment method is stored in vault + #[schema(example = "187282ab-40ef-47a9-9206-5099ba31e432")] + pub payment_token: Option, + + /// Additional data related to some connectors + #[schema(value_type = Option)] + pub connector_metadata: Option, + + /// Payment Experience for the current payment + #[schema(value_type = Option, example = "redirect_to_url")] + pub payment_experience: Option, + + /// Payment method type for the payment attempt + #[schema(value_type = Option, example = "wallet")] + pub payment_method_type: common_enums::PaymentMethod, + + /// reference(Identifier) to the payment at connector side + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_reference_id: Option, + + /// The payment method subtype for the payment attempt. + #[schema(value_type = Option, example = "apple_pay")] + pub payment_method_subtype: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_payment_id: Option, + + /// Identifier for Payment Method used for the payment attempt + #[schema(value_type = Option, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub payment_method_id: Option, + + /// Value passed in X-CLIENT-SOURCE header during payments confirm request by the client + pub client_source: Option, + /// Value passed in X-CLIENT-VERSION header during payments confirm request by the client + pub client_version: Option, +} + #[derive( Default, Debug, serde::Serialize, Clone, PartialEq, ToSchema, router_derive::PolymorphicSchema, )] @@ -5069,7 +5166,10 @@ pub struct PaymentsRetrieveRequest { /// If this is set to true, the status will be fetched from the connector #[serde(default)] pub force_sync: bool, - + /// A boolean used to indicate if all the attempts needs to be fetched for the intent. + /// If this is set to true, attempts list will be available in the response. + #[serde(default)] + pub expand_attempts: bool, /// These are the query params that are sent in case of redirect response. /// These can be ingested by the connector to take necessary actions. pub param: Option, @@ -5077,7 +5177,7 @@ pub struct PaymentsRetrieveRequest { /// Error details for the payment #[cfg(feature = "v2")] -#[derive(Debug, serde::Serialize, Clone, ToSchema)] +#[derive(Debug, serde::Serialize, Clone, PartialEq, ToSchema)] pub struct ErrorDetails { /// The error code pub code: String, @@ -5259,6 +5359,9 @@ pub struct PaymentsRetrieveResponse { /// The billing address associated with the payment intent pub billing: Option
, + + /// List of payment attempts associated with payment intent + pub attempts: Option>, } #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 9dd13e5b030..749ed41bcb4 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -242,6 +242,21 @@ impl PaymentAttempt { .await } + #[cfg(feature = "v2")] + pub async fn find_by_payment_id( + conn: &PgPooledConn, + payment_id: &common_utils::id_type::GlobalPaymentId, + ) -> StorageResult> { + generics::generic_filter::<::Table, _, _, _>( + conn, + dsl::payment_id.eq(payment_id.to_owned()), + None, + None, + Some(dsl::created_at.asc()), + ) + .await + } + #[cfg(feature = "v1")] pub async fn find_by_merchant_id_preprocessing_id( conn: &PgPooledConn, diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 095b4c33a46..939d9fee0e1 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -603,6 +603,7 @@ where pub payment_intent: PaymentIntent, pub payment_attempt: Option, pub payment_address: payment_address::PaymentAddress, + pub attempts: Option>, /// Should the payment status be synced with connector /// This will depend on the payment status and the force sync flag in the request pub should_sync_with_connector: bool, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 361973daf94..e641ee63d27 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -151,6 +151,15 @@ pub trait PaymentAttemptInterface { storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + #[cfg(feature = "v2")] + async fn find_payment_attempts_by_payment_intent_id( + &self, + state: &KeyManagerState, + payment_id: &id_type::GlobalPaymentId, + merchant_key_store: &MerchantKeyStore, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result, errors::StorageError>; + #[cfg(feature = "v1")] async fn find_payment_attempt_by_preprocessing_id_merchant_id( &self, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index f2b692c27a2..a4af7b1e131 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -460,6 +460,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::RedirectResponse, api_models::payments::RequestSurchargeDetails, api_models::payments::PaymentAttemptResponse, + api_models::payments::PaymentAttemptAmountDetails, api_models::payments::CaptureResponse, api_models::payments::PaymentsIncrementalAuthorizationRequest, api_models::payments::IncrementalAuthorizationResponse, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 592dda60177..1b4d00882aa 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -2327,6 +2327,7 @@ impl PaymentRedirectFlow for PaymentRedirectSync { let payment_sync_request = api::PaymentsRetrieveRequest { param: Some(req.query_params.clone()), force_sync: true, + expand_attempts: false, }; let operation = operations::PaymentGet; diff --git a/crates/router/src/core/payments/operations/payment_get.rs b/crates/router/src/core/payments/operations/payment_get.rs index 6419376f090..189439ec967 100644 --- a/crates/router/src/core/payments/operations/payment_get.rs +++ b/crates/router/src/core/payments/operations/payment_get.rs @@ -169,11 +169,32 @@ impl GetTracker, PaymentsRetriev Some(true), ); + let attempts = match request.expand_attempts { + true => payment_intent + .active_attempt_id + .as_ref() + .async_map(|active_attempt| async { + db.find_payment_attempts_by_payment_intent_id( + key_manager_state, + payment_id, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not find payment attempts for the given the intent id") + }) + .await + .transpose()?, + false => None, + }; + let payment_data = PaymentStatusData { flow: std::marker::PhantomData, payment_intent, payment_attempt, payment_address, + attempts, should_sync_with_connector, }; diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index e2967e4ba59..5371afca931 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1687,6 +1687,12 @@ where let error = optional_payment_attempt .and_then(|payment_attempt| payment_attempt.error.clone()) .map(api_models::payments::ErrorDetails::foreign_from); + let attempts = self.attempts.as_ref().map(|attempts| { + attempts + .iter() + .map(api_models::payments::PaymentAttemptResponse::foreign_from) + .collect() + }); let payment_method_data = Some(api_models::payments::PaymentMethodDataResponseWithBilling { @@ -1729,6 +1735,7 @@ where merchant_connector_id, browser_info: None, error, + attempts, }; Ok(services::ApplicationResponse::JsonWithHeaders(( @@ -4146,6 +4153,61 @@ impl } } +#[cfg(feature = "v2")] +impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt> + for api_models::payments::PaymentAttemptResponse +{ + fn foreign_from( + attempt: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttempt, + ) -> Self { + Self { + id: attempt.get_id().to_owned(), + status: attempt.status, + amount: api_models::payments::PaymentAttemptAmountDetails::foreign_from( + &attempt.amount_details, + ), + connector: attempt.connector.clone(), + error: attempt + .error + .clone() + .map(api_models::payments::ErrorDetails::foreign_from), + authentication_type: attempt.authentication_type, + created_at: attempt.created_at, + modified_at: attempt.modified_at, + cancellation_reason: attempt.cancellation_reason.clone(), + payment_token: attempt.payment_token.clone(), + connector_metadata: attempt.connector_metadata.clone(), + payment_experience: attempt.payment_experience, + payment_method_type: attempt.payment_method_type, + connector_reference_id: attempt.connector_response_reference_id.clone(), + payment_method_subtype: attempt.get_payment_method_type(), + connector_payment_id: attempt.get_connector_payment_id().map(ToString::to_string), + payment_method_id: attempt.payment_method_id.clone(), + client_source: attempt.client_source.clone(), + client_version: attempt.client_version.clone(), + } + } +} + +#[cfg(feature = "v2")] +impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::AttemptAmountDetails> + for api_models::payments::PaymentAttemptAmountDetails +{ + fn foreign_from( + amount: &hyperswitch_domain_models::payments::payment_attempt::AttemptAmountDetails, + ) -> Self { + Self { + net_amount: amount.get_net_amount(), + amount_to_capture: amount.get_amount_to_capture(), + surcharge_amount: amount.get_surcharge_amount(), + tax_on_surcharge: amount.get_tax_on_surcharge(), + amount_capturable: amount.get_amount_capturable(), + shipping_cost: amount.get_shipping_cost(), + order_tax_amount: amount.get_order_tax_amount(), + } + } +} + #[cfg(feature = "v2")] impl ForeignFrom for api_models::payments::ErrorDetails diff --git a/crates/router/src/core/webhooks/incoming_v2.rs b/crates/router/src/core/webhooks/incoming_v2.rs index 6b01615778e..900a455f993 100644 --- a/crates/router/src/core/webhooks/incoming_v2.rs +++ b/crates/router/src/core/webhooks/incoming_v2.rs @@ -431,6 +431,7 @@ async fn payments_incoming_webhook_flow( payments::operations::PaymentGet, api::PaymentsRetrieveRequest { force_sync: true, + expand_attempts: false, param: None, }, get_trackers_response, @@ -630,6 +631,7 @@ where flow: PhantomData, payment_intent, payment_attempt: Some(payment_attempt), + attempts: None, should_sync_with_connector: true, payment_address, }, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index b8ecb2eea83..3622695c9f2 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1618,6 +1618,24 @@ impl PaymentAttemptInterface for KafkaStore { .await } + #[cfg(feature = "v2")] + async fn find_payment_attempts_by_payment_intent_id( + &self, + key_manager_state: &KeyManagerState, + payment_id: &id_type::GlobalPaymentId, + merchant_key_store: &domain::MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result, errors::DataStorageError> { + self.diesel_store + .find_payment_attempts_by_payment_intent_id( + key_manager_state, + payment_id, + merchant_key_store, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] async fn find_payment_attempt_last_successful_attempt_by_payment_id_merchant_id( &self, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index db210dbdbe5..7bf4266bf5b 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -81,6 +81,18 @@ impl PaymentAttemptInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(feature = "v2")] + async fn find_payment_attempts_by_payment_intent_id( + &self, + _key_manager_state: &KeyManagerState, + _id: &id_type::GlobalPaymentId, + _merchant_key_store: &MerchantKeyStore, + _storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result, StorageError> { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + #[cfg(feature = "v1")] async fn find_payment_attempt_by_preprocessing_id_merchant_id( &self, diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 8a8e8d4e4a8..0c8b969e166 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -425,6 +425,43 @@ impl PaymentAttemptInterface for RouterStore { .change_context(errors::StorageError::DecryptionError) } + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn find_payment_attempts_by_payment_intent_id( + &self, + key_manager_state: &KeyManagerState, + payment_id: &common_utils::id_type::GlobalPaymentId, + merchant_key_store: &MerchantKeyStore, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result, errors::StorageError> { + use common_utils::ext_traits::AsyncExt; + + let conn = pg_connection_read(self).await?; + DieselPaymentAttempt::find_by_payment_id(&conn, payment_id) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + }) + .async_and_then(|payment_attempts| async { + let mut domain_payment_attempts = Vec::with_capacity(payment_attempts.len()); + for attempt in payment_attempts.into_iter() { + domain_payment_attempts.push( + attempt + .convert( + key_manager_state, + merchant_key_store.key.get_inner(), + merchant_key_store.merchant_id.clone().into(), + ) + .await + .change_context(errors::StorageError::DecryptionError)?, + ); + } + Ok(domain_payment_attempts) + }) + .await + } + #[cfg(all(feature = "v1", feature = "olap"))] #[instrument(skip_all)] async fn get_total_count_of_filtered_payment_attempts( @@ -1200,6 +1237,25 @@ impl PaymentAttemptInterface for KVRouterStore { .await } + #[cfg(feature = "v2")] + #[instrument(skip_all)] + async fn find_payment_attempts_by_payment_intent_id( + &self, + key_manager_state: &KeyManagerState, + payment_id: &common_utils::id_type::GlobalPaymentId, + merchant_key_store: &MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result, errors::StorageError> { + self.router_store + .find_payment_attempts_by_payment_intent_id( + key_manager_state, + payment_id, + merchant_key_store, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn find_payment_attempt_by_preprocessing_id_merchant_id( diff --git a/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/down.sql b/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/down.sql new file mode 100644 index 00000000000..42c7d02ff2d --- /dev/null +++ b/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +DROP INDEX IF EXISTS payment_attempt_payment_id_index; +CREATE INDEX IF NOT EXISTS payment_attempt_payment_id_merchant_id_index ON payment_attempt (payment_id, merchant_id); \ No newline at end of file diff --git a/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/up.sql b/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/up.sql new file mode 100644 index 00000000000..9a9a9b077cd --- /dev/null +++ b/v2_migrations/2025-02-10-101458_create_index_payment_attempt_payment_id/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +DROP INDEX IF EXISTS payment_attempt_payment_id_merchant_id_index; +CREATE INDEX IF NOT EXISTS payment_attempt_payment_id_index ON payment_attempt (payment_id); \ No newline at end of file From 96153824a73f359623bf77f199013d2ca9ff5e43 Mon Sep 17 00:00:00 2001 From: Narayan Bhat <48803246+Narayanbhat166@users.noreply.github.com> Date: Tue, 11 Feb 2025 16:59:58 +0530 Subject: [PATCH 079/133] feat(payment_methods_session_v2): add payment methods session endpoints (#7107) Co-authored-by: Sanchith Hegde Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: Anurag Thakur Co-authored-by: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Co-authored-by: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Co-authored-by: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Co-authored-by: hrithikesh026 Co-authored-by: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Co-authored-by: awasthi21 <107559116+awasthi21@users.noreply.github.com> Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Arindam Sahoo <88739246+arindam-sahoo@users.noreply.github.com> Co-authored-by: Arindam Sahoo Co-authored-by: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Co-authored-by: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Co-authored-by: Riddhiagrawal001 <50551695+Riddhiagrawal001@users.noreply.github.com> Co-authored-by: Suman Maji <77887221+sumanmaji4@users.noreply.github.com> Co-authored-by: Sandeep Kumar <83278309+tsdk02@users.noreply.github.com> Co-authored-by: Debarshi Gupta Co-authored-by: Debarshi Gupta Co-authored-by: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Co-authored-by: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Co-authored-by: pranav-arjunan Co-authored-by: Kashif Co-authored-by: Sagnik Mitra <83326850+ImSagnik007@users.noreply.github.com> Co-authored-by: sweta-kumari-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> --- .../customers--list-saved-payment-methods.mdx | 3 + .../payment-method-session--create.mdx | 3 + ...t-method-session--list-payment-methods.mdx | 3 + .../payment-method-session--retrieve.mdx | 3 + ...session--update-a-saved-payment-method.mdx | 3 + .../payment-methods--payment-methods-list.mdx | 3 + api-reference-v2/mint.json | 15 +- api-reference-v2/openapi_spec.json | 552 ++++++++--- config/development.toml | 58 +- crates/api_models/src/customers.rs | 8 +- crates/api_models/src/ephemeral_key.rs | 60 +- crates/api_models/src/events.rs | 22 + crates/api_models/src/events/payment.rs | 2 +- crates/api_models/src/payment_methods.rs | 142 ++- crates/api_models/src/payments.rs | 3 +- crates/api_models/src/webhooks.rs | 2 +- crates/common_enums/src/enums.rs | 33 + crates/common_types/src/payment_methods.rs | 20 + crates/common_utils/src/crypto.rs | 11 +- crates/common_utils/src/events.rs | 9 +- crates/common_utils/src/id_type.rs | 6 +- .../common_utils/src/id_type/client_secret.rs | 32 + .../common_utils/src/id_type/ephemeral_key.rs | 31 - crates/common_utils/src/id_type/global_id.rs | 7 +- .../src/id_type/global_id/payment_methods.rs | 84 ++ .../common_utils/src/types/authentication.rs | 25 + crates/diesel_models/src/customers.rs | 6 +- crates/diesel_models/src/ephemeral_key.rs | 37 +- crates/diesel_models/src/lib.rs | 3 + .../src/payment_methods_session.rs | 11 + .../src/connectors/deutschebank.rs | 19 + .../hyperswitch_domain_models/src/customer.rs | 6 +- .../src/payment_methods.rs | 260 ++++-- crates/openapi/src/openapi_v2.rs | 18 +- crates/openapi/src/routes/payment_method.rs | 109 ++- crates/router/src/consts.rs | 3 + crates/router/src/core/payment_methods.rs | 878 +++++++----------- .../router/src/core/payment_methods/cards.rs | 6 +- .../surcharge_decision_configs.rs | 153 +-- .../src/core/payment_methods/transformers.rs | 96 +- crates/router/src/core/payments/helpers.rs | 101 +- .../router/src/core/payments/transformers.rs | 3 +- crates/router/src/core/routing/helpers.rs | 4 +- crates/router/src/db.rs | 4 + crates/router/src/db/ephemeral_key.rs | 196 ++-- crates/router/src/db/kafka_store.rs | 81 +- .../router/src/db/payment_method_session.rs | 137 +++ crates/router/src/lib.rs | 7 +- crates/router/src/routes.rs | 2 + crates/router/src/routes/app.rs | 58 +- crates/router/src/routes/customers.rs | 16 +- crates/router/src/routes/ephemeral_key.rs | 36 +- crates/router/src/routes/lock_utils.rs | 5 + crates/router/src/routes/payment_methods.rs | 346 +++---- crates/router/src/services/authentication.rs | 318 +++++-- .../router/src/types/storage/ephemeral_key.rs | 37 +- crates/router_env/src/logger/types.rs | 6 + 57 files changed, 2671 insertions(+), 1431 deletions(-) create mode 100644 api-reference-v2/api-reference/customers/customers--list-saved-payment-methods.mdx create mode 100644 api-reference-v2/api-reference/payment-method-session/payment-method-session--create.mdx create mode 100644 api-reference-v2/api-reference/payment-method-session/payment-method-session--list-payment-methods.mdx create mode 100644 api-reference-v2/api-reference/payment-method-session/payment-method-session--retrieve.mdx create mode 100644 api-reference-v2/api-reference/payment-method-session/payment-method-session--update-a-saved-payment-method.mdx create mode 100644 api-reference-v2/api-reference/payment-methods/payment-methods--payment-methods-list.mdx create mode 100644 crates/common_utils/src/id_type/client_secret.rs delete mode 100644 crates/common_utils/src/id_type/ephemeral_key.rs create mode 100644 crates/diesel_models/src/payment_methods_session.rs create mode 100644 crates/router/src/db/payment_method_session.rs diff --git a/api-reference-v2/api-reference/customers/customers--list-saved-payment-methods.mdx b/api-reference-v2/api-reference/customers/customers--list-saved-payment-methods.mdx new file mode 100644 index 00000000000..ef5a27f9604 --- /dev/null +++ b/api-reference-v2/api-reference/customers/customers--list-saved-payment-methods.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/customers/{id}/saved-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-method-session/payment-method-session--create.mdx b/api-reference-v2/api-reference/payment-method-session/payment-method-session--create.mdx new file mode 100644 index 00000000000..b75390a47d1 --- /dev/null +++ b/api-reference-v2/api-reference/payment-method-session/payment-method-session--create.mdx @@ -0,0 +1,3 @@ +--- +openapi: post /v2/payment-method-session +--- diff --git a/api-reference-v2/api-reference/payment-method-session/payment-method-session--list-payment-methods.mdx b/api-reference-v2/api-reference/payment-method-session/payment-method-session--list-payment-methods.mdx new file mode 100644 index 00000000000..bbbaf01c297 --- /dev/null +++ b/api-reference-v2/api-reference/payment-method-session/payment-method-session--list-payment-methods.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-method-session/:id/list-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-method-session/payment-method-session--retrieve.mdx b/api-reference-v2/api-reference/payment-method-session/payment-method-session--retrieve.mdx new file mode 100644 index 00000000000..3f0ab4a7461 --- /dev/null +++ b/api-reference-v2/api-reference/payment-method-session/payment-method-session--retrieve.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-method-session/:id +--- \ No newline at end of file diff --git a/api-reference-v2/api-reference/payment-method-session/payment-method-session--update-a-saved-payment-method.mdx b/api-reference-v2/api-reference/payment-method-session/payment-method-session--update-a-saved-payment-method.mdx new file mode 100644 index 00000000000..fab85e2a419 --- /dev/null +++ b/api-reference-v2/api-reference/payment-method-session/payment-method-session--update-a-saved-payment-method.mdx @@ -0,0 +1,3 @@ +--- +openapi: put /v2/payment-method-session/:id/update-saved-payment-method +--- diff --git a/api-reference-v2/api-reference/payment-methods/payment-methods--payment-methods-list.mdx b/api-reference-v2/api-reference/payment-methods/payment-methods--payment-methods-list.mdx new file mode 100644 index 00000000000..b7f8edccffa --- /dev/null +++ b/api-reference-v2/api-reference/payment-methods/payment-methods--payment-methods-list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payment-methods/{id}/list-enabled-payment-methods +--- \ No newline at end of file diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 11e716d6303..8fd3b64230b 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -55,7 +55,17 @@ "api-reference/payment-methods/payment-method--confirm-intent", "api-reference/payment-methods/payment-method--update", "api-reference/payment-methods/payment-method--retrieve", - "api-reference/payment-methods/payment-method--delete" + "api-reference/payment-methods/payment-method--delete", + "api-reference/payment-methods/list-saved-payment-methods-for-a-customer" + ] + }, + { + "group": "Payment Method Session", + "pages": [ + "api-reference/payment-method-session/payment-method-session--create", + "api-reference/payment-method-session/payment-method-session--retrieve", + "api-reference/payment-method-session/payment-method-session--list-payment-methods", + "api-reference/payment-method-session/payment-method-session--update-a-saved-payment-method" ] }, { @@ -122,7 +132,8 @@ "api-reference/customers/customers--retrieve", "api-reference/customers/customers--update", "api-reference/customers/customers--delete", - "api-reference/customers/customers--list" + "api-reference/customers/customers--list", + "api-reference/customers/customers--list-saved-payment-methods" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 96294f56a96..ba795d40594 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -2338,58 +2338,6 @@ ] } }, - "/v2/payment-methods/{id}/list-enabled-payment-methods": { - "get": { - "tags": [ - "Payment Methods" - ], - "summary": "Payment Methods - Payment Methods List", - "description": "List the payment methods eligible for a payment method.", - "operationId": "List Payment methods for a Payment Method Intent", - "parameters": [ - { - "name": "id", - "in": "path", - "description": "The global payment method id", - "required": true, - "schema": { - "type": "string" - } - }, - { - "name": "X-Profile-Id", - "in": "header", - "description": "Profile ID associated to the payment method intent", - "required": true, - "schema": { - "type": "string" - }, - "example": "pro_abcdefghijklmnop" - } - ], - "responses": { - "200": { - "description": "Get the payment methods", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/PaymentMethodListResponseForPayments" - } - } - } - }, - "404": { - "description": "No payment method found with the given id" - } - }, - "security": [ - { - "api_key": [], - "ephemeral_key": [] - } - ] - } - }, "/v2/payment-methods/{id}/confirm-intent": { "post": { "tags": [ @@ -2550,6 +2498,198 @@ ] } }, + "/v2/payment-method-session": { + "post": { + "tags": [ + "Payment Method Session" + ], + "summary": "Payment Method Session - Create", + "description": "Create a payment method session for a customer\nThis is used to list the saved payment methods for the customer\nThe customer can also add a new payment method using this session", + "operationId": "Create a payment method session", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodSessionRequest" + }, + "examples": { + "Create a payment method session with customer_id": { + "value": { + "customer_id": "12345_cus_abcdefghijklmnopqrstuvwxyz" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Create the payment method session", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodsSessionResponse" + } + } + } + }, + "400": { + "description": "The request is invalid" + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/v2/payment-method-session/:id": { + "get": { + "tags": [ + "Payment Method Session" + ], + "summary": "Payment Method Session - Retrieve", + "description": "Retrieve the payment method session", + "operationId": "Retrieve the payment method session", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method Session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The payment method session is retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodsSessionResponse" + } + } + } + }, + "404": { + "description": "The request is invalid" + } + }, + "security": [ + { + "ephemeral_key": [] + } + ] + } + }, + "/v2/payment-method-session/:id/list-payment-methods": { + "get": { + "tags": [ + "Payment Method Session" + ], + "summary": "Payment Method Session - List Payment Methods", + "description": "List payment methods for the given payment method session.\nThis endpoint lists the enabled payment methods for the profile and the saved payment methods of the customer.", + "operationId": "List Payment methods for a Payment Method Session", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method Session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "The payment method session is retrieved successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodListResponse" + } + } + } + }, + "404": { + "description": "The request is invalid" + } + }, + "security": [ + { + "ephemeral_key": [] + } + ] + } + }, + "/v2/payment-method-session/:id/update-saved-payment-method": { + "put": { + "tags": [ + "Payment Method Session" + ], + "summary": "Payment Method Session - Update a saved payment method", + "description": "Update a saved payment method from the given payment method session.", + "operationId": "Update a saved payment method", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "The unique identifier for the Payment Method Session", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodSessionUpdateSavedPaymentMethod" + }, + "examples": { + "Update the card holder name": { + "value": { + "payment_method_data": { + "card": { + "card_holder_name": "Narayan Bhat" + } + }, + "payment_method_id": "12345_pm_0194b1ecabc172e28aeb71f70a4daba3" + } + } + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "The payment method has been updated successfully", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentMethodResponse" + } + } + } + }, + "404": { + "description": "The request is invalid" + } + }, + "security": [ + { + "ephemeral_key": [] + } + ] + } + }, "/v2/refunds": { "post": { "tags": [ @@ -6709,6 +6849,42 @@ } } }, + "ClientSecretResponse": { + "type": "object", + "description": "client_secret for the resource_id mentioned", + "required": [ + "id", + "resource_id", + "created_at", + "expires", + "secret" + ], + "properties": { + "id": { + "type": "string", + "description": "Client Secret id", + "maxLength": 32, + "minLength": 1 + }, + "resource_id": { + "$ref": "#/components/schemas/ResourceId" + }, + "created_at": { + "type": "string", + "format": "date-time", + "description": "time at which this client secret was created" + }, + "expires": { + "type": "string", + "format": "date-time", + "description": "time at which this client secret would expire" + }, + "secret": { + "type": "string", + "description": "client secret" + } + } + }, "Comparison": { "type": "object", "description": "Represents a single comparison condition.", @@ -7779,25 +7955,20 @@ "CustomerPaymentMethod": { "type": "object", "required": [ - "payment_method_id", + "id", "customer_id", "payment_method_type", + "payment_method_subtype", "recurring_enabled", "created", "requires_cvv", "is_default" ], "properties": { - "payment_token": { - "type": "string", - "description": "Token for payment method in temporary card locker which gets refreshed often", - "example": "7ebf443f-a050-4067-84e5-e6f6d4800aef", - "nullable": true - }, - "payment_method_id": { + "id": { "type": "string", - "description": "The unique identifier of the customer.", - "example": "pm_iouuy468iyuowqs" + "description": "The unique identifier of the payment method.", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8" }, "customer_id": { "type": "string", @@ -7810,12 +7981,7 @@ "$ref": "#/components/schemas/PaymentMethod" }, "payment_method_subtype": { - "allOf": [ - { - "$ref": "#/components/schemas/PaymentMethodType" - } - ], - "nullable": true + "$ref": "#/components/schemas/PaymentMethodType" }, "recurring_enabled": { "type": "boolean", @@ -7844,14 +8010,6 @@ "description": "A timestamp (ISO 8601 code) that determines when the payment method was created", "example": "2023-01-18T11:04:09.922Z" }, - "surcharge_details": { - "allOf": [ - { - "$ref": "#/components/schemas/SurchargeDetailsResponse" - } - ], - "nullable": true - }, "requires_cvv": { "type": "boolean", "description": "Whether this payment method requires CVV to be collected", @@ -7861,8 +8019,7 @@ "type": "string", "format": "date-time", "description": "A timestamp (ISO 8601 code) that determines when the payment method was last used", - "example": "2024-02-24T11:04:09.922Z", - "nullable": true + "example": "2024-02-24T11:04:09.922Z" }, "is_default": { "type": "boolean", @@ -7891,11 +8048,6 @@ "$ref": "#/components/schemas/CustomerPaymentMethod" }, "description": "List of payment methods for customer" - }, - "is_guest_customer": { - "type": "boolean", - "description": "Returns whether a customer id is not tied to a payment intent (only when the request is made against a client secret)", - "nullable": true } } }, @@ -8059,7 +8211,7 @@ "default_payment_method_id": { "type": "string", "description": "The identifier for the default payment method.", - "example": "pm_djh2837dwduh890123", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8", "nullable": true, "maxLength": 64 } @@ -8137,7 +8289,7 @@ "default_payment_method_id": { "type": "string", "description": "The unique identifier of the payment method", - "example": "card_rGK4Vi5iSW70MY7J2mIg", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8", "nullable": true } }, @@ -8492,39 +8644,6 @@ } } }, - "EphemeralKeyCreateResponse": { - "type": "object", - "description": "ephemeral_key for the customer_id mentioned", - "required": [ - "customer_id", - "created_at", - "expires", - "secret" - ], - "properties": { - "customer_id": { - "type": "string", - "description": "customer_id to which this ephemeral key belongs to", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "maxLength": 64, - "minLength": 1 - }, - "created_at": { - "type": "integer", - "format": "int64", - "description": "time at which this ephemeral key was created" - }, - "expires": { - "type": "integer", - "format": "int64", - "description": "time at which this ephemeral key would expire" - }, - "secret": { - "type": "string", - "description": "ephemeral key" - } - } - }, "ErrorCategory": { "type": "string", "enum": [ @@ -11712,6 +11831,26 @@ } } }, + "NetworkTokenization": { + "type": "object", + "description": "The network tokenization configuration for creating the payment method session", + "required": [ + "enable" + ], + "properties": { + "enable": { + "$ref": "#/components/schemas/NetworkTokenizationToggle" + } + } + }, + "NetworkTokenizationToggle": { + "type": "string", + "description": "The network tokenization toggle, whether to enable or skip the network tokenization", + "enum": [ + "Enable", + "Skip" + ] + }, "NetworkTransactionIdAndCardDetails": { "type": "object", "required": [ @@ -12394,7 +12533,7 @@ ] }, "object": { - "$ref": "#/components/schemas/PaymentsResponse" + "$ref": "#/components/schemas/PaymentsRetrieveResponse" } } }, @@ -14247,7 +14386,8 @@ "PaymentMethodListResponse": { "type": "object", "required": [ - "payment_methods_enabled" + "payment_methods_enabled", + "customer_payment_methods" ], "properties": { "payment_methods_enabled": { @@ -14256,6 +14396,13 @@ "$ref": "#/components/schemas/ResponsePaymentMethodTypes" }, "description": "The list of payment methods that are enabled for the business profile" + }, + "customer_payment_methods": { + "type": "array", + "items": { + "$ref": "#/components/schemas/CustomerPaymentMethod" + }, + "description": "The list of saved payment methods of the customer" } } }, @@ -14338,11 +14485,6 @@ "example": "2024-02-24T11:04:09.922Z", "nullable": true }, - "ephemeral_key": { - "type": "string", - "description": "For Client based calls", - "nullable": true - }, "payment_method_data": { "allOf": [ { @@ -14368,6 +14510,72 @@ } ] }, + "PaymentMethodSessionRequest": { + "type": "object", + "required": [ + "customer_id" + ], + "properties": { + "customer_id": { + "type": "string", + "description": "The customer id for which the payment methods session is to be created", + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44" + }, + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + }, + "psp_tokenization": { + "allOf": [ + { + "$ref": "#/components/schemas/PspTokenization" + } + ], + "nullable": true + }, + "network_tokenization": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTokenization" + } + ], + "nullable": true + }, + "expires_in": { + "type": "integer", + "format": "int32", + "description": "The time (seconds ) when the session will expire\nIf not provided, the session will expire in 15 minutes", + "default": 900, + "example": 900, + "nullable": true, + "minimum": 0 + } + } + }, + "PaymentMethodSessionUpdateSavedPaymentMethod": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodUpdate" + }, + { + "type": "object", + "required": [ + "payment_method_id" + ], + "properties": { + "payment_method_id": { + "type": "string", + "description": "The payment method id of the payment method to be updated", + "example": "12345_pm_01926c58bc6e77c09e809964e72af8c8" + } + } + } + ] + }, "PaymentMethodSpecificFeatures": { "oneOf": [ { @@ -14569,6 +14777,60 @@ }, "additionalProperties": false }, + "PaymentMethodsSessionResponse": { + "type": "object", + "required": [ + "id", + "customer_id", + "expires_at", + "client_secret" + ], + "properties": { + "id": { + "type": "string", + "example": "12345_pms_01926c58bc6e77c09e809964e72af8c8" + }, + "customer_id": { + "type": "string", + "description": "The customer id for which the payment methods session is to be created", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8" + }, + "billing": { + "allOf": [ + { + "$ref": "#/components/schemas/Address" + } + ], + "nullable": true + }, + "psp_tokenization": { + "allOf": [ + { + "$ref": "#/components/schemas/PspTokenization" + } + ], + "nullable": true + }, + "network_tokenization": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTokenization" + } + ], + "nullable": true + }, + "expires_at": { + "type": "string", + "format": "date-time", + "description": "The iso timestamp when the session will expire\nTrying to retrieve the session or any operations on the session after this time will result in an error", + "example": "2023-01-18T11:04:09.922Z" + }, + "client_secret": { + "type": "string", + "description": "Client Secret" + } + } + }, "PaymentProcessingDetails": { "type": "object", "required": [ @@ -17858,6 +18120,23 @@ } } }, + "PspTokenization": { + "type": "object", + "description": "The Payment Service Provider Configuration for payment methods that are created using the payment method session", + "required": [ + "tokenization_type", + "connector_id" + ], + "properties": { + "tokenization_type": { + "$ref": "#/components/schemas/TokenizationType" + }, + "connector_id": { + "type": "string", + "description": "The merchant connector id to be used for tokenization" + } + } + }, "RealTimePaymentData": { "oneOf": [ { @@ -18567,6 +18846,21 @@ } } }, + "ResourceId": { + "oneOf": [ + { + "type": "object", + "required": [ + "customer" + ], + "properties": { + "customer": { + "type": "string" + } + } + } + ] + }, "ResponsePaymentMethodTypes": { "allOf": [ { @@ -20480,6 +20774,14 @@ } } }, + "TokenizationType": { + "type": "string", + "description": "The type of tokenization to use for the payment method", + "enum": [ + "single_use", + "multi_use" + ] + }, "TouchNGoRedirection": { "type": "object" }, diff --git a/config/development.toml b/config/development.toml index fc7a9427d8c..a7e33511f17 100644 --- a/config/development.toml +++ b/config/development.toml @@ -69,8 +69,8 @@ common_merchant_identifier = "COMMON MERCHANT IDENTIFIER" applepay_endpoint = "DOMAIN SPECIFIC ENDPOINT" [locker] -host = "" -host_rs = "" +host = "http://127.0.0.1:3000" +host_rs = "http://127.0.0.1:3000" mock_locker = true basilisk_host = "" locker_enabled = true @@ -83,9 +83,57 @@ fallback_api_key = "" redis_lock_timeout = 100 [jwekey] -vault_encryption_key = "" -rust_locker_encryption_key = "" -vault_private_key = "" +vault_encryption_key = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwa6siKaSYqD1o4J3AbHq +Km8oVTvep7GoN/C45qY60C7DO72H1O7Ujt6ZsSiK83EyI0CaUg3ORPS3ayobFNmu +zR366ckK8GIf3BG7sVI6u/9751z4OvBHZMM9JFWa7Bx/RCPQ8aeM+iJoqf9auuQm +3NCTlfaZJif45pShswR+xuZTR/bqnsOSP/MFROI9ch0NE7KRogy0tvrZe21lP24i +Ro2LJJG+bYshxBddhxQf2ryJ85+/Trxdu16PunodGzCl6EMT3bvb4ZC41i15omqU +aXXV1Z1wYUhlsO0jyd1bVvjyuE/KE1TbBS0gfR/RkacODmmE2zEdZ0EyyiXwqkmc +oQIDAQAB +-----END PUBLIC KEY----- +""" +rust_locker_encryption_key = """ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAwa6siKaSYqD1o4J3AbHq +Km8oVTvep7GoN/C45qY60C7DO72H1O7Ujt6ZsSiK83EyI0CaUg3ORPS3ayobFNmu +zR366ckK8GIf3BG7sVI6u/9751z4OvBHZMM9JFWa7Bx/RCPQ8aeM+iJoqf9auuQm +3NCTlfaZJif45pShswR+xuZTR/bqnsOSP/MFROI9ch0NE7KRogy0tvrZe21lP24i +Ro2LJJG+bYshxBddhxQf2ryJ85+/Trxdu16PunodGzCl6EMT3bvb4ZC41i15omqU +aXXV1Z1wYUhlsO0jyd1bVvjyuE/KE1TbBS0gfR/RkacODmmE2zEdZ0EyyiXwqkmc +oQIDAQAB +-----END PUBLIC KEY----- +""" +vault_private_key = """ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA5Z/K0JWds8iHhWCa+rj0rhOQX1nVs/ArQ1D0vh3UlSPR2vZU +TrkdP7i3amv4d2XDC+3+5/YWExTkpxqnfl1T9J37leN2guAARed6oYoTDEP/OoKt +nUrKK2xk/+V5DNOWcRiSpcCrJOOIEoACOlPIrXQSg16KDZQb0QTMntnsiPIJDbsO +GcdKytRAcNaokiKLnia1v13N3bk6dSplrj1YzawslZfgqD0eov4FjzBMoA19yNtl +VLLf6kOkLcFQjTKXJLP1tLflLUBPTg8fm9wgAPK2BjMQ2AMkUxx0ubbtw/9CeJ+b +FWrqGnEhlvfDMlyAV77sAiIdQ4mXs3TLcLb/AQIDAQABAoIBAGNekD1N0e5AZG1S +zh6cNb6zVrH8xV9WGtLJ0PAJJrrXwnQYT4m10DOIM0+Jo/+/ePXLq5kkRI9DZmPu +Q/eKWc+tInfN9LZUS6n0r3wCrZWMQ4JFlO5RtEWwZdDbtFPZqOwObz/treKL2JHw +9YXaRijR50UUf3e61YLRqd9AfX0RNuuG8H+WgU3Gwuh5TwRnljM3JGaDPHsf7HLS +tNkqJuByp26FEbOLTokZDbHN0sy7/6hufxnIS9AK4vp8y9mZAjghG26Rbg/H71mp +Z+Q6P1y7xdgAKbhq7usG3/o4Y1e9wnghHvFS7DPwGekHQH2+LsYNEYOir1iRjXxH +GUXOhfUCgYEA+cR9jONQFco8Zp6z0wdGlBeYoUHVMjThQWEblWL2j4RG/qQd/y0j +uhVeU0/PmkYK2mgcjrh/pgDTtPymc+QuxBi+lexs89ppuJIAgMvLUiJT67SBHguP +l4+oL9U78KGh7PfJpMKH+Pk5yc1xucAevk0wWmr5Tz2vKRDavFTPV+MCgYEA61qg +Y7yN0cDtxtqlZdMC8BJPFCQ1+s3uB0wetAY3BEKjfYc2O/4sMbixXzt5PkZqZy96 +QBUBxhcM/rIolpM3nrgN7h1nmJdk9ljCTjWoTJ6fDk8BUh8+0GrVhTbe7xZ+bFUN +UioIqvfapr/q/k7Ah2mCBE04wTZFry9fndrH2ssCgYEAh1T2Cj6oiAX6UEgxe2h3 +z4oxgz6efAO3AavSPFFQ81Zi+VqHflpA/3TQlSerfxXwj4LV5mcFkzbjfy9eKXE7 +/bjCm41tQ3vWyNEjQKYr1qcO/aniRBtThHWsVa6eObX6fOGN+p4E+txfeX693j3A +6q/8QSGxUERGAmRFgMIbTq0CgYAmuTeQkXKII4U75be3BDwEgg6u0rJq/L0ASF74 +4djlg41g1wFuZ4if+bJ9Z8ywGWfiaGZl6s7q59oEgg25kKljHQd1uTLVYXuEKOB3 +e86gJK0o7ojaGTf9lMZi779IeVv9uRTDAxWAA93e987TXuPAo/R3frkq2SIoC9Rg +paGidwKBgBqYd/iOJWsUZ8cWEhSE1Huu5rDEpjra8JPXHqQdILirxt1iCA5aEQou +BdDGaDr8sepJbGtjwTyiG8gEaX1DD+KsF2+dQRQdQfcYC40n8fKkvpFwrKjDj1ac +VuY3OeNxi+dC2r7HppP3O/MJ4gX/RJJfSrcaGP8/Ke1W5+jE97Qy +-----END RSA PRIVATE KEY----- +""" tunnel_private_key = "" [connectors.supported] diff --git a/crates/api_models/src/customers.rs b/crates/api_models/src/customers.rs index fe4ba7d9686..d78564ae53a 100644 --- a/crates/api_models/src/customers.rs +++ b/crates/api_models/src/customers.rs @@ -213,8 +213,8 @@ pub struct CustomerResponse { #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, /// The identifier for the default payment method. - #[schema(max_length = 64, example = "pm_djh2837dwduh890123")] - pub default_payment_method_id: Option, + #[schema(value_type = Option, max_length = 64, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub default_payment_method_id: Option, } #[cfg(all(feature = "v2", feature = "customer_v2"))] @@ -340,8 +340,8 @@ pub struct CustomerUpdateRequest { #[schema(value_type = Option,example = json!({ "city": "NY", "unit": "245" }))] pub metadata: Option, /// The unique identifier of the payment method - #[schema(example = "card_rGK4Vi5iSW70MY7J2mIg")] - pub default_payment_method_id: Option, + #[schema(value_type = Option, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub default_payment_method_id: Option, } #[cfg(all(feature = "v2", feature = "customer_v2"))] diff --git a/crates/api_models/src/ephemeral_key.rs b/crates/api_models/src/ephemeral_key.rs index fa61642e353..9f569b91f7a 100644 --- a/crates/api_models/src/ephemeral_key.rs +++ b/crates/api_models/src/ephemeral_key.rs @@ -19,51 +19,69 @@ pub struct EphemeralKeyCreateRequest { } #[cfg(feature = "v2")] -/// Information required to create an ephemeral key. +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq, ToSchema)] +#[serde(rename_all = "snake_case")] +pub enum ResourceId { + #[schema(value_type = String)] + Customer(id_type::GlobalCustomerId), +} + +#[cfg(feature = "v2")] +/// Information required to create a client secret. #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, ToSchema)] -pub struct EphemeralKeyCreateRequest { - /// Customer ID for which an ephemeral key must be created - #[schema( - min_length = 32, - max_length = 64, - value_type = String, - example = "12345_cus_01926c58bc6e77c09e809964e72af8c8" - )] - pub customer_id: id_type::GlobalCustomerId, +pub struct ClientSecretCreateRequest { + /// Resource ID for which a client secret must be created + pub resource_id: ResourceId, } #[cfg(feature = "v2")] -/// ephemeral_key for the customer_id mentioned +/// client_secret for the resource_id mentioned #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, ToSchema)] -pub struct EphemeralKeyResponse { - /// Ephemeral key id +pub struct ClientSecretResponse { + /// Client Secret id #[schema(value_type = String, max_length = 32, min_length = 1)] - pub id: id_type::EphemeralKeyId, - /// customer_id to which this ephemeral key belongs to - #[schema(value_type = String, max_length = 64, min_length = 32, example = "12345_cus_01926c58bc6e77c09e809964e72af8c8")] - pub customer_id: id_type::GlobalCustomerId, - /// time at which this ephemeral key was created + pub id: id_type::ClientSecretId, + /// resource_id to which this client secret belongs to + #[schema(value_type = ResourceId)] + pub resource_id: ResourceId, + /// time at which this client secret was created pub created_at: time::PrimitiveDateTime, - /// time at which this ephemeral key would expire + /// time at which this client secret would expire pub expires: time::PrimitiveDateTime, #[schema(value_type=String)] - /// ephemeral key + /// client secret pub secret: Secret, } +#[cfg(feature = "v1")] impl common_utils::events::ApiEventMetric for EphemeralKeyCreateRequest { fn get_api_event_type(&self) -> Option { Some(common_utils::events::ApiEventsType::Miscellaneous) } } +#[cfg(feature = "v1")] +impl common_utils::events::ApiEventMetric for EphemeralKeyCreateResponse { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Miscellaneous) + } +} + #[cfg(feature = "v2")] -impl common_utils::events::ApiEventMetric for EphemeralKeyResponse { +impl common_utils::events::ApiEventMetric for ClientSecretCreateRequest { fn get_api_event_type(&self) -> Option { Some(common_utils::events::ApiEventsType::Miscellaneous) } } +#[cfg(feature = "v2")] +impl common_utils::events::ApiEventMetric for ClientSecretResponse { + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::Miscellaneous) + } +} + +#[cfg(feature = "v1")] /// ephemeral_key for the customer_id mentioned #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Eq, PartialEq, ToSchema)] pub struct EphemeralKeyCreateResponse { diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 27cbbd614d6..2124ff1aff9 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -179,6 +179,16 @@ impl ApiEventMetric for DisputesMetricsResponse { Some(ApiEventsType::Miscellaneous) } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl ApiEventMetric for PaymentMethodIntentConfirmInternal { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethod { + payment_method_id: self.id.clone(), + payment_method_type: Some(self.request.payment_method_type), + payment_method_subtype: Some(self.request.payment_method_subtype), + }) + } +} #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl ApiEventMetric for PaymentMethodIntentCreate { @@ -192,3 +202,15 @@ impl ApiEventMetric for DisputeListFilters { Some(ApiEventsType::ResourceListAPI) } } + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentMethodSessionRequest {} + +#[cfg(feature = "v2")] +impl ApiEventMetric for PaymentMethodsSessionResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::PaymentMethodSession { + payment_method_session_id: self.id.clone(), + }) + } +} diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index c365445e102..4e672927284 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -160,7 +160,7 @@ impl ApiEventMetric for PaymentsRequest { } #[cfg(feature = "v2")] -impl ApiEventMetric for PaymentsResponse { +impl ApiEventMetric for payments::PaymentsResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::Payment { payment_id: self.id.clone(), diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index d15fdd74432..ba18cbba506 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -194,6 +194,20 @@ impl PaymentMethodIntentConfirm { } } +/// This struct is used internally only +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] +pub struct PaymentMethodIntentConfirmInternal { + pub id: id_type::GlobalPaymentMethodId, + pub request: PaymentMethodIntentConfirm, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for PaymentMethodIntentConfirm { + fn from(item: PaymentMethodIntentConfirmInternal) -> Self { + item.request + } +} #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] /// This struct is only used by and internal api to migrate payment method pub struct PaymentMethodMigrate { @@ -871,10 +885,6 @@ pub struct PaymentMethodResponse { #[serde(default, with = "common_utils::custom_serde::iso8601::option")] pub last_used_at: Option, - /// For Client based calls - #[schema(value_type=Option)] - pub ephemeral_key: Option>, - pub payment_method_data: Option, } @@ -1105,6 +1115,9 @@ impl From for payments::AdditionalCardInfo { pub struct PaymentMethodListResponse { /// The list of payment methods that are enabled for the business profile pub payment_methods_enabled: Vec, + + /// The list of saved payment methods of the customer + pub customer_payment_methods: Vec, } #[cfg(all( @@ -1797,8 +1810,6 @@ pub struct CustomerPaymentMethodsListResponse { pub struct CustomerPaymentMethodsListResponse { /// List of payment methods for customer pub customer_payment_methods: Vec, - /// Returns whether a customer id is not tied to a payment intent (only when the request is made against a client secret) - pub is_guest_customer: Option, } #[cfg(all( @@ -1844,12 +1855,9 @@ pub struct CustomerDefaultPaymentMethodResponse { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive(Debug, Clone, serde::Serialize, ToSchema)] pub struct CustomerPaymentMethod { - /// Token for payment method in temporary card locker which gets refreshed often - #[schema(example = "7ebf443f-a050-4067-84e5-e6f6d4800aef")] - pub payment_token: Option, - /// The unique identifier of the customer. - #[schema(example = "pm_iouuy468iyuowqs")] - pub payment_method_id: String, + /// The unique identifier of the payment method. + #[schema(value_type = String, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub id: id_type::GlobalPaymentMethodId, /// The unique identifier of the customer. #[schema( @@ -1865,8 +1873,8 @@ pub struct CustomerPaymentMethod { pub payment_method_type: api_enums::PaymentMethod, /// This is a sub-category of payment method. - #[schema(value_type = Option,example = "credit_card")] - pub payment_method_subtype: Option, + #[schema(value_type = PaymentMethodType,example = "credit")] + pub payment_method_subtype: api_enums::PaymentMethodType, /// Indicates whether the payment method is eligible for recurring payments #[schema(example = true)] @@ -1884,17 +1892,15 @@ pub struct CustomerPaymentMethod { #[serde(with = "common_utils::custom_serde::iso8601")] pub created: time::PrimitiveDateTime, - /// Surcharge details for this saved card - pub surcharge_details: Option, - /// Whether this payment method requires CVV to be collected #[schema(example = true)] pub requires_cvv: bool, /// A timestamp (ISO 8601 code) that determines when the payment method was last used - #[schema(value_type = Option,example = "2024-02-24T11:04:09.922Z")] - #[serde(default, with = "common_utils::custom_serde::iso8601::option")] - pub last_used_at: Option, + #[schema(value_type = PrimitiveDateTime,example = "2024-02-24T11:04:09.922Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601")] + pub last_used_at: time::PrimitiveDateTime, + /// Indicates if the payment method has been set to default or not #[schema(example = true)] pub is_default: bool, @@ -2299,6 +2305,7 @@ type PaymentMethodMigrationResponseType = ( Result, PaymentMethodRecord, ); + #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") @@ -2464,29 +2471,72 @@ impl From<(PaymentMethodRecord, id_type::MerchantId)> for customers::CustomerReq } } -// #[cfg(feature = "v2")] -// impl From for customers::CustomerRequest { -// fn from(record: PaymentMethodRecord) -> Self { -// Self { -// merchant_reference_id: Some(record.customer_id), -// name: record.name.unwrap(), -// email: record.email.unwrap(), -// phone: record.phone, -// description: None, -// phone_country_code: record.phone_country_code, -// default_billing_address: Some(payments::AddressDetails { -// city: Some(record.billing_address_city), -// country: record.billing_address_country, -// line1: Some(record.billing_address_line1), -// line2: record.billing_address_line2, -// state: Some(record.billing_address_state), -// line3: record.billing_address_line3, -// zip: Some(record.billing_address_zip), -// first_name: Some(record.billing_address_first_name), -// last_name: Some(record.billing_address_last_name), -// }), -// default_shipping_address: None, -// metadata: None, -// } -// } -// } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentMethodSessionRequest { + /// The customer id for which the payment methods session is to be created + #[schema(value_type = String, example = "cus_y3oqhf46pyzuxjbcn2giaqnb44")] + pub customer_id: id_type::GlobalCustomerId, + + /// The billing address details of the customer. This will also be used for any new payment methods added during the session + #[schema(value_type = Option
)] + pub billing: Option, + + /// The tokenization type to be applied + #[schema(value_type = Option)] + pub psp_tokenization: Option, + + /// The network tokenization configuration if applicable + #[schema(value_type = Option)] + pub network_tokenization: Option, + + /// The time (seconds ) when the session will expire + /// If not provided, the session will expire in 15 minutes + #[schema(example = 900, default = 900)] + pub expires_in: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentMethodSessionUpdateSavedPaymentMethod { + /// The payment method id of the payment method to be updated + #[schema(value_type = String, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] + pub payment_method_id: id_type::GlobalPaymentMethodId, + + /// The update request for the payment method update + #[serde(flatten)] + pub payment_method_update_request: PaymentMethodUpdate, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PaymentMethodsSessionResponse { + #[schema(value_type = String, example = "12345_pms_01926c58bc6e77c09e809964e72af8c8")] + pub id: id_type::GlobalPaymentMethodSessionId, + + /// The customer id for which the payment methods session is to be created + #[schema(value_type = String, example = "12345_cus_01926c58bc6e77c09e809964e72af8c8")] + pub customer_id: id_type::GlobalCustomerId, + + /// The billing address details of the customer. This will also be used for any new payment methods added during the session + #[schema(value_type = Option
)] + pub billing: Option, + + /// The tokenization type to be applied + #[schema(value_type = Option)] + pub psp_tokenization: Option, + + /// The network tokenization configuration if applicable + #[schema(value_type = Option)] + pub network_tokenization: Option, + + /// The iso timestamp when the session will expire + /// Trying to retrieve the session or any operations on the session after this time will result in an error + #[schema(value_type = PrimitiveDateTime, example = "2023-01-18T11:04:09.922Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub expires_at: time::PrimitiveDateTime, + + /// Client Secret + #[schema(value_type = String)] + pub client_secret: masking::Secret, +} diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 295b1979f1f..820992025c1 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -27,12 +27,13 @@ use time::{Date, PrimitiveDateTime}; use url::Url; use utoipa::ToSchema; +#[cfg(feature = "v1")] +use crate::ephemeral_key::EphemeralKeyCreateResponse; #[cfg(feature = "v2")] use crate::payment_methods; use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, - ephemeral_key::EphemeralKeyCreateResponse, mandates::RecurringDetails, refunds, }; diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index f99e3a450b2..ddefea542c2 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -285,7 +285,7 @@ pub enum OutgoingWebhookContent { #[serde(tag = "type", content = "object", rename_all = "snake_case")] #[cfg(feature = "v2")] pub enum OutgoingWebhookContent { - #[schema(value_type = PaymentsResponse, title = "PaymentsResponse")] + #[schema(value_type = PaymentsRetrieveResponse, title = "PaymentsResponse")] PaymentDetails(Box), #[schema(value_type = RefundResponse, title = "RefundResponse")] RefundDetails(Box), diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index c4ee48ed5ac..2a5219f4dea 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3687,6 +3687,39 @@ pub enum FeatureStatus { Supported, } +/// The type of tokenization to use for the payment method +#[derive( + Clone, + Copy, + Debug, + Eq, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + ToSchema, +)] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum TokenizationType { + /// Create a single use token for the given payment method + /// The user might have to go through additional factor authentication when using the single use token if required by the payment method + SingleUse, + /// Create a multi use token for the given payment method + /// User will have to complete the additional factor authentication only once when creating the multi use token + /// This will create a mandate at the connector which can be used for recurring payments + MultiUse, +} + +/// The network tokenization toggle, whether to enable or skip the network tokenization +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub enum NetworkTokenizationToggle { + /// Enable network tokenization for the payment method + Enable, + /// Skip network tokenization for the payment method + Skip, +} + #[derive(Clone, Copy, Debug, Deserialize, Serialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum GooglePayAuthMethod { diff --git a/crates/common_types/src/payment_methods.rs b/crates/common_types/src/payment_methods.rs index 702d8c0e700..9e80a029a22 100644 --- a/crates/common_types/src/payment_methods.rs +++ b/crates/common_types/src/payment_methods.rs @@ -124,3 +124,23 @@ where } common_utils::impl_to_sql_from_sql_json!(PaymentMethodsEnabled); + +/// The network tokenization configuration for creating the payment method session +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct NetworkTokenization { + /// Enable the network tokenization for payment methods that are created using the payment method session + #[schema(value_type = NetworkTokenizationToggle)] + pub enable: common_enums::NetworkTokenizationToggle, +} + +/// The Payment Service Provider Configuration for payment methods that are created using the payment method session +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct PspTokenization { + /// The tokenization type to be applied for the payment method + #[schema(value_type = TokenizationType)] + pub tokenization_type: common_enums::TokenizationType, + + /// The merchant connector id to be used for tokenization + #[schema(value_type = String)] + pub connector_id: common_utils::id_type::MerchantConnectorAccountId, +} diff --git a/crates/common_utils/src/crypto.rs b/crates/common_utils/src/crypto.rs index e8c2b2d5a0b..52fc0daacf5 100644 --- a/crates/common_utils/src/crypto.rs +++ b/crates/common_utils/src/crypto.rs @@ -484,12 +484,21 @@ impl Encryptable { F: FnOnce(T) -> CustomResult, U: Clone, { - // Option::map(self, f) let inner = self.inner; let encrypted = self.encrypted; let inner = f(inner)?; Ok(Encryptable { inner, encrypted }) } + + /// consume self and modify the inner value + pub fn map(self, f: impl FnOnce(T) -> U) -> Encryptable { + let encrypted_data = self.encrypted; + let masked_data = f(self.inner); + Encryptable { + inner: masked_data, + encrypted: encrypted_data, + } + } } impl Deref for Encryptable> { diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index bae525f4562..2e90d646e9e 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -112,8 +112,13 @@ pub enum ApiEventsType { poll_id: String, }, Analytics, - EphemeralKey { - key_id: id_type::EphemeralKeyId, + #[cfg(feature = "v2")] + ClientSecret { + key_id: id_type::ClientSecretId, + }, + #[cfg(feature = "v2")] + PaymentMethodSession { + payment_method_session_id: id_type::GlobalPaymentMethodSessionId, }, } diff --git a/crates/common_utils/src/id_type.rs b/crates/common_utils/src/id_type.rs index ea90c00121e..30b6a3888da 100644 --- a/crates/common_utils/src/id_type.rs +++ b/crates/common_utils/src/id_type.rs @@ -2,8 +2,8 @@ //! The id type can be used to create specific id types with custom behaviour mod api_key; +mod client_secret; mod customer; -mod ephemeral_key; #[cfg(feature = "v2")] mod global_id; mod merchant; @@ -32,14 +32,14 @@ use thiserror::Error; pub use self::global_id::{ customer::GlobalCustomerId, payment::{GlobalAttemptId, GlobalPaymentId}, - payment_methods::GlobalPaymentMethodId, + payment_methods::{GlobalPaymentMethodId, GlobalPaymentMethodSessionId}, refunds::GlobalRefundId, CellId, }; pub use self::{ api_key::ApiKeyId, + client_secret::ClientSecretId, customer::CustomerId, - ephemeral_key::EphemeralKeyId, merchant::MerchantId, merchant_connector_account::MerchantConnectorAccountId, organization::OrganizationId, diff --git a/crates/common_utils/src/id_type/client_secret.rs b/crates/common_utils/src/id_type/client_secret.rs new file mode 100644 index 00000000000..b9d7f2e09e6 --- /dev/null +++ b/crates/common_utils/src/id_type/client_secret.rs @@ -0,0 +1,32 @@ +crate::id_type!( + ClientSecretId, + "A type for key_id that can be used for Ephemeral key IDs" +); +crate::impl_id_type_methods!(ClientSecretId, "key_id"); + +// This is to display the `ClientSecretId` as ClientSecretId(abcd) +crate::impl_debug_id_type!(ClientSecretId); +crate::impl_try_from_cow_str_id_type!(ClientSecretId, "key_id"); + +crate::impl_generate_id_id_type!(ClientSecretId, "csi"); +crate::impl_serializable_secret_id_type!(ClientSecretId); +crate::impl_queryable_id_type!(ClientSecretId); +crate::impl_to_sql_from_sql_id_type!(ClientSecretId); + +#[cfg(feature = "v2")] +impl crate::events::ApiEventMetric for ClientSecretId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::ClientSecret { + key_id: self.clone(), + }) + } +} + +crate::impl_default_id_type!(ClientSecretId, "key"); + +impl ClientSecretId { + /// Generate a key for redis + pub fn generate_redis_key(&self) -> String { + format!("cs_{}", self.get_string_repr()) + } +} diff --git a/crates/common_utils/src/id_type/ephemeral_key.rs b/crates/common_utils/src/id_type/ephemeral_key.rs deleted file mode 100644 index 071980fc6a4..00000000000 --- a/crates/common_utils/src/id_type/ephemeral_key.rs +++ /dev/null @@ -1,31 +0,0 @@ -crate::id_type!( - EphemeralKeyId, - "A type for key_id that can be used for Ephemeral key IDs" -); -crate::impl_id_type_methods!(EphemeralKeyId, "key_id"); - -// This is to display the `EphemeralKeyId` as EphemeralKeyId(abcd) -crate::impl_debug_id_type!(EphemeralKeyId); -crate::impl_try_from_cow_str_id_type!(EphemeralKeyId, "key_id"); - -crate::impl_generate_id_id_type!(EphemeralKeyId, "eki"); -crate::impl_serializable_secret_id_type!(EphemeralKeyId); -crate::impl_queryable_id_type!(EphemeralKeyId); -crate::impl_to_sql_from_sql_id_type!(EphemeralKeyId); - -impl crate::events::ApiEventMetric for EphemeralKeyId { - fn get_api_event_type(&self) -> Option { - Some(crate::events::ApiEventsType::EphemeralKey { - key_id: self.clone(), - }) - } -} - -crate::impl_default_id_type!(EphemeralKeyId, "key"); - -impl EphemeralKeyId { - /// Generate a key for redis - pub fn generate_redis_key(&self) -> String { - format!("epkey_{}", self.get_string_repr()) - } -} diff --git a/crates/common_utils/src/id_type/global_id.rs b/crates/common_utils/src/id_type/global_id.rs index 1ad1bd96084..1e376dfe4de 100644 --- a/crates/common_utils/src/id_type/global_id.rs +++ b/crates/common_utils/src/id_type/global_id.rs @@ -5,7 +5,6 @@ pub(super) mod refunds; use diesel::{backend::Backend, deserialize::FromSql, serialize::ToSql, sql_types}; use error_stack::ResultExt; -use serde_json::error; use thiserror::Error; use crate::{ @@ -27,6 +26,7 @@ pub(crate) enum GlobalEntity { Attempt, PaymentMethod, Refund, + PaymentMethodSession, } impl GlobalEntity { @@ -37,6 +37,7 @@ impl GlobalEntity { Self::PaymentMethod => "pm", Self::Attempt => "att", Self::Refund => "ref", + Self::PaymentMethodSession => "pms", } } } @@ -204,8 +205,8 @@ mod global_id_tests { let cell_id = CellId::from_str(cell_id_string).unwrap(); let global_id = GlobalId::generate(&cell_id, entity); - /// Generate a regex for globalid - /// Eg - 12abc_cus_abcdefghijklmnopqrstuvwxyz1234567890 + // Generate a regex for globalid + // Eg - 12abc_cus_abcdefghijklmnopqrstuvwxyz1234567890 let regex = regex::Regex::new(r"[a-z0-9]{5}_cus_[a-z0-9]{32}").unwrap(); assert!(regex.is_match(&global_id.0 .0 .0)); diff --git a/crates/common_utils/src/id_type/global_id/payment_methods.rs b/crates/common_utils/src/id_type/global_id/payment_methods.rs index a4fec5e836a..b3b314e7fcb 100644 --- a/crates/common_utils/src/id_type/global_id/payment_methods.rs +++ b/crates/common_utils/src/id_type/global_id/payment_methods.rs @@ -19,12 +19,61 @@ use crate::{ #[diesel(sql_type = diesel::sql_types::Text)] pub struct GlobalPaymentMethodId(GlobalId); +/// A global id that can be used to identify a payment method session +#[derive( + Debug, + Clone, + Hash, + PartialEq, + Eq, + serde::Serialize, + serde::Deserialize, + diesel::expression::AsExpression, +)] +#[diesel(sql_type = diesel::sql_types::Text)] +pub struct GlobalPaymentMethodSessionId(GlobalId); + #[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] pub enum GlobalPaymentMethodIdError { #[error("Failed to construct GlobalPaymentMethodId")] ConstructionError, } +#[derive(Debug, thiserror::Error, Clone, PartialEq, Eq)] +pub enum GlobalPaymentMethodSessionIdError { + #[error("Failed to construct GlobalPaymentMethodSessionId")] + ConstructionError, +} + +impl GlobalPaymentMethodSessionId { + /// Create a new GlobalPaymentMethodSessionId from cell id information + pub fn generate( + cell_id: &CellId, + ) -> error_stack::Result { + let global_id = GlobalId::generate(cell_id, GlobalEntity::PaymentMethodSession); + Ok(Self(global_id)) + } + + /// Get the string representation of the id + pub fn get_string_repr(&self) -> &str { + self.0.get_string_repr() + } + + /// Construct a redis key from the id to be stored in redis + pub fn get_redis_key(&self) -> String { + format!("payment_method_session:{}", self.get_string_repr()) + } +} + +#[cfg(feature = "v2")] +impl crate::events::ApiEventMetric for GlobalPaymentMethodSessionId { + fn get_api_event_type(&self) -> Option { + Some(crate::events::ApiEventsType::PaymentMethodSession { + payment_method_session_id: self.clone(), + }) + } +} + impl crate::events::ApiEventMetric for GlobalPaymentMethodId { fn get_api_event_type(&self) -> Option { Some( @@ -89,3 +138,38 @@ where Ok(Self(global_id)) } } + +impl diesel::Queryable for GlobalPaymentMethodSessionId +where + DB: diesel::backend::Backend, + Self: diesel::deserialize::FromSql, +{ + type Row = Self; + fn build(row: Self::Row) -> diesel::deserialize::Result { + Ok(row) + } +} + +impl diesel::serialize::ToSql for GlobalPaymentMethodSessionId +where + DB: diesel::backend::Backend, + GlobalId: diesel::serialize::ToSql, +{ + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } +} + +impl diesel::deserialize::FromSql for GlobalPaymentMethodSessionId +where + DB: diesel::backend::Backend, + GlobalId: diesel::deserialize::FromSql, +{ + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + let global_id = GlobalId::from_sql(value)?; + Ok(Self(global_id)) + } +} diff --git a/crates/common_utils/src/types/authentication.rs b/crates/common_utils/src/types/authentication.rs index 2df81b2d3d4..331f1e2a659 100644 --- a/crates/common_utils/src/types/authentication.rs +++ b/crates/common_utils/src/types/authentication.rs @@ -35,3 +35,28 @@ pub enum AuthInfo { profile_ids: Vec, }, } + +/// Enum for different resource types supported in client secret +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, PartialEq, Eq)] +#[serde(rename_all = "snake_case")] +pub enum ResourceId { + /// Global Payment ID (Not exposed in api_models version of enum) + Payment(id_type::GlobalPaymentId), + /// Global Customer ID + Customer(id_type::GlobalCustomerId), + /// Global Payment Methods Session ID + PaymentMethodSession(id_type::GlobalPaymentMethodSessionId), +} + +#[cfg(feature = "v2")] +impl ResourceId { + /// Get string representation of enclosed ID type + pub fn to_str(&self) -> &str { + match self { + Self::Payment(id) => id.get_string_repr(), + Self::Customer(id) => id.get_string_repr(), + Self::PaymentMethodSession(id) => id.get_string_repr(), + } + } +} diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index 7bd7d8368a7..dc39059c135 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -84,7 +84,7 @@ pub struct CustomerNew { pub metadata: Option, pub connector_customer: Option, pub modified_at: PrimitiveDateTime, - pub default_payment_method_id: Option, + pub default_payment_method_id: Option, pub updated_by: Option, pub version: ApiVersion, pub merchant_reference_id: Option, @@ -166,7 +166,7 @@ pub struct Customer { pub metadata: Option, pub connector_customer: Option, pub modified_at: PrimitiveDateTime, - pub default_payment_method_id: Option, + pub default_payment_method_id: Option, pub updated_by: Option, pub version: ApiVersion, pub merchant_reference_id: Option, @@ -243,7 +243,7 @@ pub struct CustomerUpdateInternal { pub metadata: Option, pub modified_at: PrimitiveDateTime, pub connector_customer: Option, - pub default_payment_method_id: Option>, + pub default_payment_method_id: Option>, pub updated_by: Option, pub default_billing_address: Option, pub default_shipping_address: Option, diff --git a/crates/diesel_models/src/ephemeral_key.rs b/crates/diesel_models/src/ephemeral_key.rs index c7fc103ed09..ede9f61b03e 100644 --- a/crates/diesel_models/src/ephemeral_key.rs +++ b/crates/diesel_models/src/ephemeral_key.rs @@ -1,30 +1,29 @@ #[cfg(feature = "v2")] use masking::{PeekInterface, Secret}; + #[cfg(feature = "v2")] -pub struct EphemeralKeyTypeNew { - pub id: common_utils::id_type::EphemeralKeyId, +pub struct ClientSecretTypeNew { + pub id: common_utils::id_type::ClientSecretId, pub merchant_id: common_utils::id_type::MerchantId, - pub customer_id: common_utils::id_type::GlobalCustomerId, pub secret: Secret, - pub resource_type: ResourceType, + pub resource_id: common_utils::types::authentication::ResourceId, } #[cfg(feature = "v2")] #[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] -pub struct EphemeralKeyType { - pub id: common_utils::id_type::EphemeralKeyId, +pub struct ClientSecretType { + pub id: common_utils::id_type::ClientSecretId, pub merchant_id: common_utils::id_type::MerchantId, - pub customer_id: common_utils::id_type::GlobalCustomerId, - pub resource_type: ResourceType, + pub resource_id: common_utils::types::authentication::ResourceId, pub created_at: time::PrimitiveDateTime, pub expires: time::PrimitiveDateTime, pub secret: Secret, } #[cfg(feature = "v2")] -impl EphemeralKeyType { +impl ClientSecretType { pub fn generate_secret_key(&self) -> String { - format!("epkey_{}", self.secret.peek()) + format!("cs_{}", self.secret.peek()) } } @@ -50,21 +49,3 @@ impl common_utils::events::ApiEventMetric for EphemeralKey { Some(common_utils::events::ApiEventsType::Miscellaneous) } } - -#[derive( - Clone, - Copy, - Debug, - serde::Serialize, - serde::Deserialize, - strum::Display, - strum::EnumString, - PartialEq, - Eq, -)] -#[serde(rename_all = "snake_case")] -#[strum(serialize_all = "snake_case")] -pub enum ResourceType { - Payment, - PaymentMethod, -} diff --git a/crates/diesel_models/src/lib.rs b/crates/diesel_models/src/lib.rs index 1908edb8ad2..d9163b02aad 100644 --- a/crates/diesel_models/src/lib.rs +++ b/crates/diesel_models/src/lib.rs @@ -47,6 +47,9 @@ pub mod routing_algorithm; pub mod types; pub mod unified_translations; +#[cfg(feature = "v2")] +pub mod payment_methods_session; + #[allow(unused_qualifications)] pub mod schema; #[allow(unused_qualifications)] diff --git a/crates/diesel_models/src/payment_methods_session.rs b/crates/diesel_models/src/payment_methods_session.rs new file mode 100644 index 00000000000..dad3e995e01 --- /dev/null +++ b/crates/diesel_models/src/payment_methods_session.rs @@ -0,0 +1,11 @@ +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] +pub struct PaymentMethodsSession { + pub id: common_utils::id_type::GlobalPaymentMethodSessionId, + pub customer_id: common_utils::id_type::GlobalCustomerId, + pub billing: Option, + pub psp_tokenization: Option, + pub network_tokeinzation: Option, + #[serde(with = "common_utils::custom_serde::iso8601")] + pub expires_at: time::PrimitiveDateTime, +} diff --git a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs index 0b92cb2538c..b481c3e808d 100644 --- a/crates/hyperswitch_connectors/src/connectors/deutschebank.rs +++ b/crates/hyperswitch_connectors/src/connectors/deutschebank.rs @@ -1058,6 +1058,25 @@ lazy_static! { } ); + deutschebank_supported_payment_methods.add( + enums::PaymentMethod::Card, + enums::PaymentMethodType::Debit, + PaymentMethodDetails{ + mandates: enums::FeatureStatus::NotSupported, + refunds: enums::FeatureStatus::Supported, + supported_capture_methods: supported_capture_methods.clone(), + specific_features: Some( + api_models::feature_matrix::PaymentMethodSpecificFeatures::Card({ + api_models::feature_matrix::CardSpecificFeatures { + three_ds: common_enums::FeatureStatus::Supported, + no_three_ds: common_enums::FeatureStatus::NotSupported, + supported_card_networks: supported_card_network.clone(), + } + }), + ), + } + ); + deutschebank_supported_payment_methods }; diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index b503326f84c..58103c08faa 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -58,7 +58,7 @@ pub struct Customer { pub metadata: Option, pub connector_customer: Option, pub modified_at: PrimitiveDateTime, - pub default_payment_method_id: Option, + pub default_payment_method_id: Option, pub updated_by: Option, pub merchant_reference_id: Option, pub default_billing_address: Option, @@ -313,14 +313,14 @@ pub enum CustomerUpdate { connector_customer: Box>, default_billing_address: Option, default_shipping_address: Option, - default_payment_method_id: Option>, + default_payment_method_id: Option>, status: Option, }, ConnectorCustomer { connector_customer: Option, }, UpdateDefaultPaymentMethod { - default_payment_method_id: Option>, + default_payment_method_id: Option>, }, } diff --git a/crates/hyperswitch_domain_models/src/payment_methods.rs b/crates/hyperswitch_domain_models/src/payment_methods.rs index 71ad9ee9275..c8683919f7b 100644 --- a/crates/hyperswitch_domain_models/src/payment_methods.rs +++ b/crates/hyperswitch_domain_models/src/payment_methods.rs @@ -1,5 +1,7 @@ +#[cfg(feature = "v2")] +use api_models::payment_methods::PaymentMethodsData; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use common_utils::crypto::Encryptable; +use common_utils::{crypto::Encryptable, encryption::Encryption, types::keymanager::ToEncryptable}; use common_utils::{ crypto::OptionalEncryptableValue, errors::{CustomResult, ParsingError, ValidationError}, @@ -9,10 +11,15 @@ use common_utils::{ use diesel_models::enums as storage_enums; use error_stack::ResultExt; use masking::{PeekInterface, Secret}; +// specific imports because of using the macro +#[cfg(feature = "v2")] +use rustc_hash::FxHashMap; +#[cfg(feature = "v2")] +use serde_json::Value; use time::PrimitiveDateTime; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use crate::type_encryption::OptionalEncryptableJsonType; +use crate::{address::Address, type_encryption::OptionalEncryptableJsonType}; use crate::{ mandates::{CommonMandateReference, PaymentsMandateReference}, type_encryption::{crypto_operation, AsyncLift, CryptoOperation}, @@ -75,16 +82,22 @@ pub struct PaymentMethod { } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[derive(Clone, Debug)] +#[derive(Clone, Debug, router_derive::ToEncryption)] pub struct PaymentMethod { + /// The identifier for the payment method. Using this recurring payments can be made + pub id: common_utils::id_type::GlobalPaymentMethodId, + + /// The customer id against which the payment method is saved pub customer_id: common_utils::id_type::GlobalCustomerId, + + /// The merchant id against which the payment method is saved pub merchant_id: common_utils::id_type::MerchantId, pub created_at: PrimitiveDateTime, pub last_modified: PrimitiveDateTime, pub payment_method_type: Option, pub payment_method_subtype: Option, - pub payment_method_data: - OptionalEncryptableJsonType, + #[encrypt(ty = Value)] + pub payment_method_data: Option>, pub locker_id: Option, pub last_used_at: PrimitiveDateTime, pub connector_mandate_details: Option, @@ -92,14 +105,15 @@ pub struct PaymentMethod { pub status: storage_enums::PaymentMethodStatus, pub network_transaction_id: Option, pub client_secret: Option, - pub payment_method_billing_address: OptionalEncryptableValue, + #[encrypt(ty = Value)] + pub payment_method_billing_address: Option>, pub updated_by: Option, pub locker_fingerprint_id: Option, - pub id: common_utils::id_type::GlobalPaymentMethodId, pub version: common_enums::ApiVersion, pub network_token_requestor_reference_id: Option, pub network_token_locker_id: Option, - pub network_token_payment_method_data: OptionalEncryptableValue, + #[encrypt(ty = Value)] + pub network_token_payment_method_data: Option>, } impl PaymentMethod { @@ -431,76 +445,88 @@ impl super::behaviour::Conversion for PaymentMethod { async fn convert_back( state: &keymanager::KeyManagerState, - item: Self::DstType, + storage_model: Self::DstType, key: &Secret>, key_manager_identifier: keymanager::Identifier, ) -> CustomResult where Self: Sized, { + use common_utils::ext_traits::ValueExt; + use masking::ExposeInterface; + async { + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedPaymentMethod::to_encryptable( + EncryptedPaymentMethod { + payment_method_data: storage_model.payment_method_data, + payment_method_billing_address: storage_model + .payment_method_billing_address, + network_token_payment_method_data: storage_model + .network_token_payment_method_data, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let data = EncryptedPaymentMethod::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; + + let payment_method_billing_address = data + .payment_method_billing_address + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + + let payment_method_data = data + .payment_method_data + .map(|payment_method_data| { + payment_method_data + .deserialize_inner_value(|value| value.parse_value("Payment Method Data")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Payment Method Data")?; + + let network_token_payment_method_data = + data.network_token_payment_method_data + .map(|network_token_payment_method_data| { + network_token_payment_method_data.map(|value| value.expose()) + }); + Ok::>(Self { - customer_id: item.customer_id, - merchant_id: item.merchant_id, - id: item.id, - created_at: item.created_at, - last_modified: item.last_modified, - payment_method_type: item.payment_method_type_v2, - payment_method_subtype: item.payment_method_subtype, - payment_method_data: item - .payment_method_data - .async_lift(|inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await?, - locker_id: item.locker_id.map(VaultId::generate), - last_used_at: item.last_used_at, - connector_mandate_details: item.connector_mandate_details.map(|cmd| cmd.into()), - customer_acceptance: item.customer_acceptance, - status: item.status, - network_transaction_id: item.network_transaction_id, - client_secret: item.client_secret, - payment_method_billing_address: item - .payment_method_billing_address - .async_lift(|inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await?, - updated_by: item.updated_by, - locker_fingerprint_id: item.locker_fingerprint_id, - version: item.version, - network_token_requestor_reference_id: item.network_token_requestor_reference_id, - network_token_locker_id: item.network_token_locker_id, - network_token_payment_method_data: item - .network_token_payment_method_data - .async_lift(|inner| async { - crypto_operation( - state, - type_name!(Self::DstType), - CryptoOperation::DecryptOptional(inner), - key_manager_identifier.clone(), - key.peek(), - ) - .await - .and_then(|val| val.try_into_optionaloperation()) - }) - .await?, + customer_id: storage_model.customer_id, + merchant_id: storage_model.merchant_id, + id: storage_model.id, + created_at: storage_model.created_at, + last_modified: storage_model.last_modified, + payment_method_type: storage_model.payment_method_type_v2, + payment_method_subtype: storage_model.payment_method_subtype, + payment_method_data, + locker_id: storage_model.locker_id.map(VaultId::generate), + last_used_at: storage_model.last_used_at, + connector_mandate_details: storage_model.connector_mandate_details.map(From::from), + customer_acceptance: storage_model.customer_acceptance, + status: storage_model.status, + network_transaction_id: storage_model.network_transaction_id, + client_secret: storage_model.client_secret, + payment_method_billing_address, + updated_by: storage_model.updated_by, + locker_fingerprint_id: storage_model.locker_fingerprint_id, + version: storage_model.version, + network_token_requestor_reference_id: storage_model + .network_token_requestor_reference_id, + network_token_locker_id: storage_model.network_token_locker_id, + network_token_payment_method_data, }) } .await @@ -541,6 +567,100 @@ impl super::behaviour::Conversion for PaymentMethod { } } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, router_derive::ToEncryption)] +pub struct PaymentMethodsSession { + pub id: common_utils::id_type::GlobalPaymentMethodSessionId, + pub customer_id: common_utils::id_type::GlobalCustomerId, + #[encrypt(ty = Value)] + pub billing: Option>, + pub psp_tokenization: Option, + pub network_tokenization: Option, + pub expires_at: PrimitiveDateTime, +} + +#[cfg(feature = "v2")] +#[async_trait::async_trait] +impl super::behaviour::Conversion for PaymentMethodsSession { + type DstType = diesel_models::payment_methods_session::PaymentMethodsSession; + type NewDstType = diesel_models::payment_methods_session::PaymentMethodsSession; + async fn convert(self) -> CustomResult { + Ok(Self::DstType { + id: self.id, + customer_id: self.customer_id, + billing: self.billing.map(|val| val.into()), + psp_tokenization: self.psp_tokenization, + network_tokeinzation: self.network_tokenization, + expires_at: self.expires_at, + }) + } + + async fn convert_back( + state: &keymanager::KeyManagerState, + storage_model: Self::DstType, + key: &Secret>, + key_manager_identifier: keymanager::Identifier, + ) -> CustomResult + where + Self: Sized, + { + use common_utils::ext_traits::ValueExt; + + async { + let decrypted_data = crypto_operation( + state, + type_name!(Self::DstType), + CryptoOperation::BatchDecrypt(EncryptedPaymentMethodsSession::to_encryptable( + EncryptedPaymentMethodsSession { + billing: storage_model.billing, + }, + )), + key_manager_identifier, + key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation())?; + + let data = EncryptedPaymentMethodsSession::from_encryptable(decrypted_data) + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Invalid batch operation data")?; + + let billing = data + .billing + .map(|billing| { + billing.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(common_utils::errors::CryptoError::DecodingFailed) + .attach_printable("Error while deserializing Address")?; + + Ok::>(Self { + id: storage_model.id, + customer_id: storage_model.customer_id, + billing, + psp_tokenization: storage_model.psp_tokenization, + network_tokenization: storage_model.network_tokeinzation, + expires_at: storage_model.expires_at, + }) + } + .await + .change_context(ValidationError::InvalidValue { + message: "Failed while decrypting payment method data".to_string(), + }) + } + + async fn construct_new(self) -> CustomResult { + Ok(Self::NewDstType { + id: self.id, + customer_id: self.customer_id, + billing: self.billing.map(|val| val.into()), + psp_tokenization: self.psp_tokenization, + network_tokeinzation: self.network_tokenization, + expires_at: self.expires_at, + }) + } +} + #[cfg(all( any(feature = "v1", feature = "v2"), not(feature = "payment_methods_v2") diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index a4af7b1e131..cc6f1b0e411 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -131,12 +131,16 @@ Never share your secret api keys. Keep them guarded and secure. //Routes for payment methods routes::payment_method::create_payment_method_api, routes::payment_method::create_payment_method_intent_api, - routes::payment_method::list_payment_methods, routes::payment_method::confirm_payment_method_intent_api, routes::payment_method::payment_method_update_api, routes::payment_method::payment_method_retrieve_api, routes::payment_method::payment_method_delete_api, - // routes::payment_method::list_customer_payment_method_api, + + //Routes for payment method session + routes::payment_method::payment_method_session_create, + routes::payment_method::payment_method_session_retrieve, + routes::payment_method::payment_method_session_list_payment_methods, + routes::payment_method::payment_method_session_update_saved_payment_method, //Routes for refunds routes::refunds::refunds_create, @@ -164,6 +168,8 @@ Never share your secret api keys. Keep them guarded and secure. common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, common_types::payment_methods::PaymentMethodsEnabled, + common_types::payment_methods::PspTokenization, + common_types::payment_methods::NetworkTokenization, common_types::refunds::SplitRefund, common_types::payments::ConnectorChargeResponseData, common_types::payments::StripeChargeResponseData, @@ -193,6 +199,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::customers::CustomerRequest, api_models::customers::CustomerUpdateRequest, api_models::customers::CustomerDeleteResponse, + api_models::ephemeral_key::ResourceId, api_models::payment_methods::PaymentMethodCreate, api_models::payment_methods::PaymentMethodIntentCreate, api_models::payment_methods::PaymentMethodIntentConfirm, @@ -493,6 +500,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payment_methods::PaymentMethodCollectLinkRequest, api_models::payment_methods::PaymentMethodCollectLinkResponse, api_models::payment_methods::PaymentMethodSubtypeSpecificData, + api_models::payment_methods::PaymentMethodSessionRequest, + api_models::payment_methods::PaymentMethodsSessionResponse, api_models::payments::PaymentsRetrieveResponse, api_models::refunds::RefundListRequest, api_models::refunds::RefundListResponse, @@ -502,7 +511,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::mandates::MandateCardDetails, api_models::mandates::RecurringDetails, api_models::mandates::ProcessorPaymentToken, - api_models::ephemeral_key::EphemeralKeyCreateResponse, + api_models::ephemeral_key::ClientSecretResponse, api_models::payments::CustomerDetails, api_models::payments::GiftCardData, api_models::payments::GiftCardDetails, @@ -655,7 +664,10 @@ Never share your secret api keys. Keep them guarded and secure. api_models::feature_matrix::PaymentMethodSpecificFeatures, api_models::feature_matrix::CardSpecificFeatures, api_models::feature_matrix::SupportedPaymentMethod, + api_models::payment_methods::PaymentMethodSessionUpdateSavedPaymentMethod, common_utils::types::BrowserInformation, + api_models::enums::TokenizationType, + api_models::enums::NetworkTokenizationToggle, api_models::payments::PaymentAmountDetailsResponse, routes::payments::ForceSync, )), diff --git a/crates/openapi/src/routes/payment_method.rs b/crates/openapi/src/routes/payment_method.rs index 4c0c201e290..3c1f66e6e76 100644 --- a/crates/openapi/src/routes/payment_method.rs +++ b/crates/openapi/src/routes/payment_method.rs @@ -322,27 +322,104 @@ pub async fn payment_method_update_api() {} #[cfg(feature = "v2")] pub async fn payment_method_delete_api() {} -/// Payment Methods - Payment Methods List +/// Payment Method Session - Create /// -/// List the payment methods eligible for a payment method. +/// Create a payment method session for a customer +/// This is used to list the saved payment methods for the customer +/// The customer can also add a new payment method using this session +#[cfg(feature = "v2")] +#[utoipa::path( + post, + path = "/v2/payment-method-session", + request_body( + content = PaymentMethodSessionRequest, + examples (( "Create a payment method session with customer_id" = ( + value =json!( { + "customer_id": "12345_cus_abcdefghijklmnopqrstuvwxyz" + }) + ))) + ), + responses( + (status = 200, description = "Create the payment method session", body = PaymentMethodsSessionResponse), + (status = 400, description = "The request is invalid") + ), + tag = "Payment Method Session", + operation_id = "Create a payment method session", + security(("api_key" = [])) +)] +pub fn payment_method_session_create() {} + +/// Payment Method Session - Retrieve +/// +/// Retrieve the payment method session #[cfg(feature = "v2")] #[utoipa::path( get, - path = "/v2/payment-methods/{id}/list-enabled-payment-methods", - params( - ("id" = String, Path, description = "The global payment method id"), - ( - "X-Profile-Id" = String, Header, - description = "Profile ID associated to the payment method intent", - example = "pro_abcdefghijklmnop" - ), + path = "/v2/payment-method-session/:id", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method Session"), ), responses( - (status = 200, description = "Get the payment methods", body = PaymentMethodListResponseForPayments), - (status = 404, description = "No payment method found with the given id") + (status = 200, description = "The payment method session is retrieved successfully", body = PaymentMethodsSessionResponse), + (status = 404, description = "The request is invalid") ), - tag = "Payment Methods", - operation_id = "List Payment methods for a Payment Method Intent", - security(("api_key" = [], "ephemeral_key" = [])) + tag = "Payment Method Session", + operation_id = "Retrieve the payment method session", + security(("ephemeral_key" = [])) +)] +pub fn payment_method_session_retrieve() {} + +/// Payment Method Session - List Payment Methods +/// +/// List payment methods for the given payment method session. +/// This endpoint lists the enabled payment methods for the profile and the saved payment methods of the customer. +#[cfg(feature = "v2")] +#[utoipa::path( + get, + path = "/v2/payment-method-session/:id/list-payment-methods", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method Session"), + ), + responses( + (status = 200, description = "The payment method session is retrieved successfully", body = PaymentMethodListResponse), + (status = 404, description = "The request is invalid") + ), + tag = "Payment Method Session", + operation_id = "List Payment methods for a Payment Method Session", + security(("ephemeral_key" = [])) +)] +pub fn payment_method_session_list_payment_methods() {} + +/// Payment Method Session - Update a saved payment method +/// +/// Update a saved payment method from the given payment method session. +#[cfg(feature = "v2")] +#[utoipa::path( + put, + path = "/v2/payment-method-session/:id/update-saved-payment-method", + params ( + ("id" = String, Path, description = "The unique identifier for the Payment Method Session"), + ), + request_body( + content = PaymentMethodSessionUpdateSavedPaymentMethod, + examples(( "Update the card holder name" = ( + value =json!( { + "payment_method_id": "12345_pm_0194b1ecabc172e28aeb71f70a4daba3", + "payment_method_data": { + "card": { + "card_holder_name": "Narayan Bhat" + } + } + } + ) + ))) + ), + responses( + (status = 200, description = "The payment method has been updated successfully", body = PaymentMethodResponse), + (status = 404, description = "The request is invalid") + ), + tag = "Payment Method Session", + operation_id = "Update a saved payment method", + security(("ephemeral_key" = [])) )] -pub fn list_payment_methods() {} +pub fn payment_method_session_update_saved_payment_method() {} diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 4d506449589..05d08f37ddd 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -215,5 +215,8 @@ pub const AUTHENTICATION_SERVICE_ELIGIBLE_CONFIG: &str = /// Refund flow identifier used for performing GSM operations pub const REFUND_FLOW_STR: &str = "refund_flow"; +/// Default payment method session expiry +pub const DEFAULT_PAYMENT_METHOD_SESSION_EXPIRY: u32 = 15 * 60; // 15 minutes + /// Authorize flow identifier used for performing GSM operations pub const AUTHORIZE_FLOW_STR: &str = "Authorize"; diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index d9f0db4c2ac..5bdb0f0819e 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -67,8 +67,10 @@ use crate::{ services::encryption, types::{ api::{self, payment_methods::PaymentMethodCreateExt}, + domain::types as domain_types, payment_methods as pm_types, storage::{ephemeral_key, PaymentMethodListContext}, + transformers::{ForeignFrom, ForeignTryFrom}, }, utils::ext_traits::OptionExt, }; @@ -852,6 +854,8 @@ pub async fn create_payment_method( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, ) -> RouterResponse { + use common_utils::ext_traits::ValueExt; + req.validate()?; let db = &*state.store; @@ -870,14 +874,20 @@ pub async fn create_payment_method( .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) .attach_printable("Customer not found for the payment method")?; - let payment_method_billing_address: Option>> = req + let payment_method_billing_address = req .billing .clone() .async_map(|billing| cards::create_encrypted_data(key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt Payment method billing address")?; + .attach_printable("Unable to encrypt Payment method billing address")? + .map(|encoded_address| { + encoded_address.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to parse Payment method billing address")?; // create pm let payment_method_id = @@ -885,7 +895,7 @@ pub async fn create_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to generate GlobalPaymentMethodId")?; - let (payment_method, ephemeral_key) = create_payment_method_for_intent( + let payment_method = create_payment_method_for_intent( state, req.metadata.clone(), &customer_id, @@ -935,10 +945,7 @@ pub async fn create_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update payment method in db")?; - let resp = pm_transforms::generate_payment_method_response( - &payment_method, - Some(ephemeral_key), - )?; + let resp = pm_transforms::generate_payment_method_response(&payment_method)?; Ok(resp) } @@ -989,14 +996,20 @@ pub async fn payment_method_intent_create( .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) .attach_printable("Customer not found for the payment method")?; - let payment_method_billing_address: Option>> = req + let payment_method_billing_address = req .billing .clone() .async_map(|billing| cards::create_encrypted_data(key_manager_state, key_store, billing)) .await .transpose() .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to encrypt Payment method billing address")?; + .attach_printable("Unable to encrypt Payment method billing address")? + .map(|encoded_address| { + encoded_address.deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to parse Payment method billing address")?; // create pm entry @@ -1005,7 +1018,7 @@ pub async fn payment_method_intent_create( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to generate GlobalPaymentMethodId")?; - let (payment_method, ephemeral_key) = create_payment_method_for_intent( + let payment_method = create_payment_method_for_intent( state, req.metadata.clone(), &customer_id, @@ -1018,134 +1031,72 @@ pub async fn payment_method_intent_create( .await .attach_printable("Failed to add Payment method to DB")?; - let resp = - pm_transforms::generate_payment_method_response(&payment_method, Some(ephemeral_key))?; + let resp = pm_transforms::generate_payment_method_response(&payment_method)?; Ok(services::ApplicationResponse::Json(resp)) } +#[cfg(feature = "v2")] +trait PerformFilteringOnEnabledPaymentMethods { + fn perform_filtering(self) -> FilteredPaymentMethodsEnabled; +} + +#[cfg(feature = "v2")] +impl PerformFilteringOnEnabledPaymentMethods + for hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled +{ + fn perform_filtering(self) -> FilteredPaymentMethodsEnabled { + FilteredPaymentMethodsEnabled(self.payment_methods_enabled) + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] -pub async fn payment_method_intent_confirm( - state: &SessionState, - req: api::PaymentMethodIntentConfirm, - merchant_account: &domain::MerchantAccount, - key_store: &domain::MerchantKeyStore, - pm_id: id_type::GlobalPaymentMethodId, -) -> RouterResponse { - let key_manager_state = &(state).into(); - req.validate()?; +pub async fn list_payment_methods_for_session( + state: SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + profile: domain::Profile, + payment_method_session_id: id_type::GlobalPaymentMethodSessionId, +) -> RouterResponse { + let key_manager_state = &(&state).into(); let db = &*state.store; - let payment_method = db - .find_payment_method( - &(state.into()), - key_store, - &pm_id, - merchant_account.storage_scheme, - ) + let payment_method_session = db + .get_payment_methods_session(key_manager_state, &key_store, &payment_method_session_id) .await .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) .attach_printable("Unable to find payment method")?; - when( - payment_method.status != enums::PaymentMethodStatus::AwaitingData, - || { - Err(errors::ApiErrorResponse::InvalidRequestData { - message: "Invalid pm_id provided: This Payment method cannot be confirmed" - .to_string(), - }) - }, - )?; - - let customer_id = payment_method.customer_id.to_owned(); - db.find_customer_by_global_id( - key_manager_state, - &customer_id, - merchant_account.get_id(), - key_store, - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - - let payment_method_data = pm_types::PaymentMethodVaultingData::from(req.payment_method_data); + let payment_connector_accounts = db + .list_enabled_connector_accounts_by_profile_id( + key_manager_state, + profile.get_id(), + &key_store, + common_enums::ConnectorType::PaymentProcessor, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("error when fetching merchant connector accounts")?; - let vaulting_result = vault_payment_method( - state, - &payment_method_data, - merchant_account, - key_store, - None, + let customer_payment_methods = list_customer_payment_method_core( + &state, + &merchant_account, + &key_store, + &payment_method_session.customer_id, ) - .await; - - let response = match vaulting_result { - Ok((vaulting_resp, fingerprint_id)) => { - let pm_update = create_pm_additional_data_update( - &payment_method_data, - state, - key_store, - Some(vaulting_resp.vault_id.get_string_repr().clone()), - Some(req.payment_method_type), - Some(req.payment_method_subtype), - Some(fingerprint_id), - ) - .await - .attach_printable("Unable to create Payment method data")?; - - let payment_method = db - .update_payment_method( - &(state.into()), - key_store, - payment_method, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update payment method in db")?; - - let resp = pm_transforms::generate_payment_method_response(&payment_method, None)?; - - Ok(resp) - } - Err(e) => { - let pm_update = storage::PaymentMethodUpdate::StatusUpdate { - status: Some(enums::PaymentMethodStatus::Inactive), - }; - - db.update_payment_method( - &(state.into()), - key_store, - payment_method, - pm_update, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to update payment method in db")?; - - Err(e) - } - }?; - - Ok(services::ApplicationResponse::Json(response)) -} + .await?; -#[cfg(feature = "v2")] -trait PerformFilteringOnEnabledPaymentMethods { - fn perform_filtering(self) -> FilteredPaymentMethodsEnabled; -} + let response = + hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts) + .perform_filtering() + .get_required_fields(RequiredFieldsInput::new(state.conf.required_fields.clone())) + .generate_response(customer_payment_methods.customer_payment_methods); -#[cfg(feature = "v2")] -impl PerformFilteringOnEnabledPaymentMethods - for hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled -{ - fn perform_filtering(self) -> FilteredPaymentMethodsEnabled { - FilteredPaymentMethodsEnabled(self.payment_methods_enabled) - } + Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( + response, + )) } #[cfg(feature = "v2")] @@ -1262,7 +1213,10 @@ struct RequiredFieldsForEnabledPaymentMethodTypes(Vec payment_methods::PaymentMethodListResponse { + fn generate_response( + self, + customer_payment_methods: Vec, + ) -> payment_methods::PaymentMethodListResponse { let response_payment_methods = self .0 .into_iter() @@ -1278,121 +1232,11 @@ impl RequiredFieldsForEnabledPaymentMethodTypes { payment_methods::PaymentMethodListResponse { payment_methods_enabled: response_payment_methods, + customer_payment_methods, } } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all)] -pub async fn list_payment_methods_enabled( - state: SessionState, - merchant_account: domain::MerchantAccount, - key_store: domain::MerchantKeyStore, - profile: domain::Profile, - payment_method_id: id_type::GlobalPaymentMethodId, -) -> RouterResponse { - let key_manager_state = &(&state).into(); - - let db = &*state.store; - - db.find_payment_method( - key_manager_state, - &key_store, - &payment_method_id, - merchant_account.storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::PaymentMethodNotFound) - .attach_printable("Unable to find payment method")?; - - let payment_connector_accounts = db - .list_enabled_connector_accounts_by_profile_id( - key_manager_state, - profile.get_id(), - &key_store, - common_enums::ConnectorType::PaymentProcessor, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("error when fetching merchant connector accounts")?; - - let response = - hyperswitch_domain_models::merchant_connector_account::FlattenedPaymentMethodsEnabled::from_payment_connectors_list(payment_connector_accounts) - .perform_filtering() - .get_required_fields(RequiredFieldsInput::new(state.conf.required_fields.clone())) - .generate_response(); - - Ok(hyperswitch_domain_models::api::ApplicationResponse::Json( - response, - )) -} - -#[cfg(all( - feature = "v2", - feature = "payment_methods_v2", - feature = "customer_v2" -))] -#[instrument(skip_all)] -#[allow(clippy::too_many_arguments)] -pub async fn create_payment_method_in_db( - state: &SessionState, - req: &api::PaymentMethodCreate, - customer_id: &id_type::GlobalCustomerId, - payment_method_id: id_type::GlobalPaymentMethodId, - locker_id: Option, - merchant_id: &id_type::MerchantId, - customer_acceptance: Option, - payment_method_data: domain::types::OptionalEncryptableJsonType< - api::payment_methods::PaymentMethodsData, - >, - key_store: &domain::MerchantKeyStore, - connector_mandate_details: Option, - status: Option, - network_transaction_id: Option, - storage_scheme: enums::MerchantStorageScheme, - payment_method_billing_address: crypto::OptionalEncryptableValue, - card_scheme: Option, -) -> errors::CustomResult { - let db = &*state.store; - let current_time = common_utils::date_time::now(); - - let response = db - .insert_payment_method( - &state.into(), - key_store, - domain::PaymentMethod { - customer_id: customer_id.to_owned(), - merchant_id: merchant_id.to_owned(), - id: payment_method_id, - locker_id, - payment_method_type: Some(req.payment_method_type), - payment_method_subtype: Some(req.payment_method_subtype), - payment_method_data, - connector_mandate_details, - customer_acceptance, - client_secret: None, - status: status.unwrap_or(enums::PaymentMethodStatus::Active), - network_transaction_id: network_transaction_id.to_owned(), - created_at: current_time, - last_modified: current_time, - last_used_at: current_time, - payment_method_billing_address, - updated_by: None, - version: domain::consts::API_VERSION, - locker_fingerprint_id: None, - network_token_locker_id: None, - network_token_payment_method_data: None, - network_token_requestor_reference_id: None, - }, - storage_scheme, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to add payment method in db")?; - - Ok(response) -} - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] #[allow(clippy::too_many_arguments)] @@ -1404,18 +1248,11 @@ pub async fn create_payment_method_for_intent( merchant_id: &id_type::MerchantId, key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, - payment_method_billing_address: crypto::OptionalEncryptableValue, -) -> errors::CustomResult<(domain::PaymentMethod, Secret), errors::ApiErrorResponse> { + payment_method_billing_address: Option< + Encryptable, + >, +) -> errors::CustomResult { let db = &*state.store; - let ephemeral_key = payment_helpers::create_ephemeral_key( - state, - customer_id, - merchant_id, - ephemeral_key::ResourceType::PaymentMethod, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to create ephemeral_key")?; let current_time = common_utils::date_time::now(); @@ -1453,7 +1290,7 @@ pub async fn create_payment_method_for_intent( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to add payment method in db")?; - Ok((response, ephemeral_key.secret)) + Ok(response) } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -1537,12 +1374,14 @@ pub async fn vault_payment_method( Ok((resp_from_vault, fingerprint_id_from_vault)) } +// TODO: check if this function will be used for listing the customer payment methods for payments +#[allow(unused)] #[cfg(all( feature = "v2", feature = "payment_methods_v2", feature = "customer_v2" ))] -async fn get_pm_list_context( +fn get_pm_list_context( payment_method_type: enums::PaymentMethod, payment_method: &domain::PaymentMethod, is_payment_associated: bool, @@ -1550,7 +1389,7 @@ async fn get_pm_list_context( let payment_method_data = payment_method .payment_method_data .clone() - .map(|payment_method_data| payment_method_data.into_inner().expose().into_inner()); + .map(|payment_method_data| payment_method_data.into_inner()); let payment_method_retrieval_context = match payment_method_data { Some(payment_methods::PaymentMethodsData::Card(card)) => { @@ -1629,316 +1468,39 @@ async fn get_pm_list_context( feature = "payment_methods_v2", feature = "customer_v2" ))] -pub async fn list_customer_payment_method_util( - state: SessionState, - merchant_account: domain::MerchantAccount, - profile: domain::Profile, - key_store: domain::MerchantKeyStore, - req: Option, - customer_id: Option, - is_payment_associated: bool, -) -> RouterResponse { - let limit = req.as_ref().and_then(|pml_req| pml_req.limit); - - let (customer_id, payment_intent) = if is_payment_associated { - let cloned_secret = req.and_then(|r| r.client_secret.clone()); - let payment_intent = payment_helpers::verify_payment_intent_time_and_client_secret( - &state, - &merchant_account, - &key_store, - cloned_secret, - ) - .await?; - - ( - payment_intent - .as_ref() - .and_then(|pi| pi.customer_id.clone()), - payment_intent, - ) - } else { - (customer_id, None) - }; - - let resp = if let Some(cust) = customer_id { - Box::pin(list_customer_payment_method( - &state, - &merchant_account, - profile, - key_store, - payment_intent, - &cust, - limit, - is_payment_associated, - )) - .await? - } else { - let response = api::CustomerPaymentMethodsListResponse { - customer_payment_methods: Vec::new(), - is_guest_customer: Some(true), - }; - services::ApplicationResponse::Json(response) - }; - - Ok(resp) -} - -#[allow(clippy::too_many_arguments)] -#[cfg(all( - feature = "v2", - feature = "payment_methods_v2", - feature = "customer_v2" -))] -pub async fn list_customer_payment_method( +pub async fn list_customer_payment_method_core( state: &SessionState, merchant_account: &domain::MerchantAccount, - profile: domain::Profile, - key_store: domain::MerchantKeyStore, - payment_intent: Option, + key_store: &domain::MerchantKeyStore, customer_id: &id_type::GlobalCustomerId, - limit: Option, - is_payment_associated: bool, -) -> RouterResponse { +) -> RouterResult { let db = &*state.store; let key_manager_state = &(state).into(); - let customer = db - .find_customer_by_global_id( - key_manager_state, - customer_id, - merchant_account.get_id(), - &key_store, - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - - let payments_info = payment_intent - .async_map(|pi| { - pm_types::SavedPMLPaymentsInfo::form_payments_info( - pi, - merchant_account, - profile, - db, - key_manager_state, - &key_store, - ) - }) - .await - .transpose()?; - let saved_payment_methods = db .find_payment_method_by_global_customer_id_merchant_id_status( key_manager_state, - &key_store, + key_store, customer_id, merchant_account.get_id(), common_enums::PaymentMethodStatus::Active, - limit, + None, merchant_account.storage_scheme, ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentMethodNotFound)?; - let mut filtered_saved_payment_methods_ctx = Vec::new(); - for payment_method in saved_payment_methods.into_iter() { - let payment_method_type = payment_method - .get_payment_method_type() - .get_required_value("payment_method")?; - let parent_payment_method_token = - is_payment_associated.then(|| generate_id(consts::ID_LENGTH, "token")); - - let pm_list_context = - get_pm_list_context(payment_method_type, &payment_method, is_payment_associated) - .await?; - - if let Some(ctx) = pm_list_context { - filtered_saved_payment_methods_ctx.push(( - ctx, - parent_payment_method_token, - payment_method, - )); - } - } - - let merchant_connector_accounts = if filtered_saved_payment_methods_ctx.iter().any( - |(_pm_list_context, _parent_payment_method_token, pm)| { - pm.connector_mandate_details.is_some() - }, - ) { - db.find_merchant_connector_account_by_merchant_id_and_disabled_list( - key_manager_state, - merchant_account.get_id(), - true, - &key_store, - ) - .await - .change_context(errors::ApiErrorResponse::MerchantAccountNotFound)? - } else { - Vec::new() - }; - let merchant_connector_accounts = - domain::MerchantConnectorAccounts::new(merchant_connector_accounts); - - let pm_list_futures = filtered_saved_payment_methods_ctx + let customer_payment_methods = saved_payment_methods .into_iter() - .map(|ctx| { - generate_saved_pm_response( - state, - &key_store, - merchant_account, - &merchant_connector_accounts, - ctx, - &customer, - payments_info.as_ref(), - ) - }) - .collect::>(); - - let customer_pms = futures::future::join_all(pm_list_futures) - .await - .into_iter() - .collect::, _>>() - .attach_printable("Failed to obtain customer payment methods")?; - - let mut response = api::CustomerPaymentMethodsListResponse { - customer_payment_methods: customer_pms, - is_guest_customer: is_payment_associated.then_some(false), //to return this key only when the request is tied to a payment intent - }; - - /* - TODO: Implement surcharge for v2 - if is_payment_associated { - Box::pin(cards::perform_surcharge_ops( - payments_info.as_ref().map(|pi| pi.payment_intent.clone()), - state, - merchant_account, - key_store, - payments_info.map(|pi| pi.profile), - &mut response, - )) - .await?; - } - */ - - Ok(services::ApplicationResponse::Json(response)) -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -async fn generate_saved_pm_response( - state: &SessionState, - key_store: &domain::MerchantKeyStore, - merchant_account: &domain::MerchantAccount, - merchant_connector_accounts: &domain::MerchantConnectorAccounts, - (pm_list_context, parent_payment_method_token, pm): ( - PaymentMethodListContext, - Option, - domain::PaymentMethod, - ), - customer: &domain::Customer, - payment_info: Option<&pm_types::SavedPMLPaymentsInfo>, -) -> Result> { - let payment_method_type = pm - .get_payment_method_type() - .get_required_value("payment_method_type")?; - - let bank_details = if payment_method_type == enums::PaymentMethod::BankDebit { - cards::get_masked_bank_details(&pm) - .await - .unwrap_or_else(|err| { - logger::error!(error=?err); - None - }) - } else { - None - }; - - let payment_method_billing = pm - .payment_method_billing_address - .clone() - .map(|decrypted_data| decrypted_data.into_inner().expose()) - .map(|decrypted_value| decrypted_value.parse_value("payment_method_billing_address")) - .transpose() - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to parse payment method billing address details")?; - - let (is_connector_agnostic_mit_enabled, requires_cvv, off_session_payment_flag, profile_id) = - payment_info - .map(|pi| { - ( - pi.is_connector_agnostic_mit_enabled, - pi.collect_cvv_during_payment, - pi.off_session_payment_flag, - Some(pi.profile.get_id().to_owned()), - ) - }) - .unwrap_or((false, false, false, Default::default())); - - let mca_enabled = cards::get_mca_status( - state, - key_store, - profile_id, - merchant_account.get_id(), - is_connector_agnostic_mit_enabled, - pm.connector_mandate_details.as_ref(), - pm.network_transaction_id.as_ref(), - merchant_connector_accounts, - ) - .await; - - let requires_cvv = if is_connector_agnostic_mit_enabled { - requires_cvv - && !(off_session_payment_flag - && (pm.connector_mandate_details.is_some() || pm.network_transaction_id.is_some())) - } else { - requires_cvv && !(off_session_payment_flag && pm.connector_mandate_details.is_some()) - }; - - let pmd = match &pm_list_context { - PaymentMethodListContext::Card { card_details, .. } => { - Some(api::PaymentMethodListData::Card(card_details.clone())) - } - #[cfg(feature = "payouts")] - PaymentMethodListContext::BankTransfer { - bank_transfer_details, - .. - } => Some(api::PaymentMethodListData::Bank( - bank_transfer_details.clone(), - )), - PaymentMethodListContext::Bank { .. } | PaymentMethodListContext::TemporaryToken { .. } => { - None - } - }; + .map(ForeignTryFrom::foreign_try_from) + .collect::, _>>() + .change_context(errors::ApiErrorResponse::InternalServerError)?; - let pma = api::CustomerPaymentMethod { - payment_token: parent_payment_method_token.clone(), - payment_method_id: pm.get_id().get_string_repr().to_owned(), - customer_id: pm.customer_id.to_owned(), - payment_method_type, - payment_method_subtype: pm.get_payment_method_subtype(), - payment_method_data: pmd, - recurring_enabled: mca_enabled, - created: pm.created_at, - bank: bank_details, - surcharge_details: None, - requires_cvv: requires_cvv - && !(off_session_payment_flag && pm.connector_mandate_details.is_some()), - last_used_at: Some(pm.last_used_at), - is_default: customer - .default_payment_method_id - .as_ref() - .is_some_and(|payment_method_id| payment_method_id == pm.get_id().get_string_repr()), - billing: payment_method_billing, + let response = api::CustomerPaymentMethodsListResponse { + customer_payment_methods, }; - payment_info - .async_map(|pi| { - pi.perform_payment_ops(state, parent_payment_method_token, &pma, pm_list_context) - }) - .await - .transpose()?; - - Ok(pma) + Ok(response) } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -1972,7 +1534,7 @@ pub async fn retrieve_payment_method( let pmd = payment_method .payment_method_data .clone() - .map(|x| x.into_inner().expose().into_inner()) + .map(|x| x.into_inner()) .and_then(|pmd| match pmd { api::PaymentMethodsData::Card(card) => { Some(api::PaymentMethodResponseData::Card(card.into())) @@ -1989,7 +1551,6 @@ pub async fn retrieve_payment_method( created: Some(payment_method.created_at), recurring_enabled: false, last_used_at: Some(payment_method.last_used_at), - ephemeral_key: None, payment_method_data: pmd, }; @@ -2001,21 +1562,33 @@ pub async fn retrieve_payment_method( pub async fn update_payment_method( state: SessionState, merchant_account: domain::MerchantAccount, - req: api::PaymentMethodUpdate, - payment_method_id: &str, key_store: domain::MerchantKeyStore, + req: api::PaymentMethodUpdate, + payment_method_id: &id_type::GlobalPaymentMethodId, ) -> RouterResponse { - let db = state.store.as_ref(); + let response = + update_payment_method_core(state, merchant_account, key_store, req, payment_method_id) + .await?; - let pm_id = id_type::GlobalPaymentMethodId::generate_from_string(payment_method_id.to_string()) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to generate GlobalPaymentMethodId")?; + Ok(services::ApplicationResponse::Json(response)) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all)] +pub async fn update_payment_method_core( + state: SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + req: api::PaymentMethodUpdate, + payment_method_id: &id_type::GlobalPaymentMethodId, +) -> RouterResult { + let db = state.store.as_ref(); let payment_method = db .find_payment_method( &((&state).into()), &key_store, - &pm_id, + payment_method_id, merchant_account.storage_scheme, ) .await @@ -2076,11 +1649,11 @@ pub async fn update_payment_method( .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Failed to update payment method in db")?; - let response = pm_transforms::generate_payment_method_response(&payment_method, None)?; + let response = pm_transforms::generate_payment_method_response(&payment_method)?; // Add a PT task to handle payment_method delete from vault - Ok(services::ApplicationResponse::Json(response)) + Ok(response) } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -2135,6 +1708,7 @@ pub async fn delete_payment_method( let pm_update = storage::PaymentMethodUpdate::StatusUpdate { status: Some(enums::PaymentMethodStatus::Inactive), }; + db.update_payment_method( &((&state).into()), &key_store, @@ -2156,6 +1730,216 @@ pub async fn delete_payment_method( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(feature = "v2")] +#[async_trait::async_trait] +trait EncryptableData { + type Output; + async fn encrypt_data( + &self, + key_manager_state: &common_utils::types::keymanager::KeyManagerState, + key_store: &domain::MerchantKeyStore, + ) -> RouterResult; +} + +#[cfg(feature = "v2")] +#[async_trait::async_trait] +impl EncryptableData for payment_methods::PaymentMethodSessionRequest { + type Output = hyperswitch_domain_models::payment_methods::DecryptedPaymentMethodsSession; + + async fn encrypt_data( + &self, + key_manager_state: &common_utils::types::keymanager::KeyManagerState, + key_store: &domain::MerchantKeyStore, + ) -> RouterResult { + use common_utils::types::keymanager::ToEncryptable; + + let encrypted_billing_address = self + .billing + .clone() + .map(|address| address.encode_to_value()) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encode billing address")? + .map(Secret::new); + + let batch_encrypted_data = domain_types::crypto_operation( + key_manager_state, + common_utils::type_name!(hyperswitch_domain_models::payment_methods::PaymentMethodsSession), + domain_types::CryptoOperation::BatchEncrypt( + hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession::to_encryptable( + hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession { + billing: encrypted_billing_address, + }, + ), + ), + common_utils::types::keymanager::Identifier::Merchant(key_store.merchant_id.clone()), + key_store.key.peek(), + ) + .await + .and_then(|val| val.try_into_batchoperation()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment methods session details".to_string())?; + + let encrypted_data = + hyperswitch_domain_models::payment_methods::FromRequestEncryptablePaymentMethodsSession::from_encryptable( + batch_encrypted_data, + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed while encrypting payment methods session detailss")?; + + Ok(encrypted_data) + } +} + +#[cfg(feature = "v2")] +pub async fn payment_methods_session_create( + state: SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + request: payment_methods::PaymentMethodSessionRequest, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + db.find_customer_by_global_id( + key_manager_state, + &request.customer_id, + merchant_account.get_id(), + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; + + let payment_methods_session_id = + id_type::GlobalPaymentMethodSessionId::generate(&state.conf.cell_information.id) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to generate GlobalPaymentMethodSessionId")?; + + let encrypted_data = request + .encrypt_data(key_manager_state, &key_store) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to encrypt payment methods session data")?; + + let billing = encrypted_data + .billing + .as_ref() + .map(|data| { + data.clone() + .deserialize_inner_value(|value| value.parse_value("Address")) + }) + .transpose() + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to decode billing address")?; + + // If not passed in the request, use the default value from constants + let expires_in = request + .expires_in + .unwrap_or(consts::DEFAULT_PAYMENT_METHOD_SESSION_EXPIRY) + .into(); + + let expires_at = common_utils::date_time::now().saturating_add(Duration::seconds(expires_in)); + + let client_secret = payment_helpers::create_client_secret( + &state, + merchant_account.get_id(), + util_types::authentication::ResourceId::PaymentMethodSession( + payment_methods_session_id.clone(), + ), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to create client secret")?; + + let payment_method_session_domain_model = + hyperswitch_domain_models::payment_methods::PaymentMethodsSession { + id: payment_methods_session_id, + customer_id: request.customer_id, + billing, + psp_tokenization: request.psp_tokenization, + network_tokenization: request.network_tokenization, + expires_at, + }; + + db.insert_payment_methods_session( + key_manager_state, + &key_store, + payment_method_session_domain_model.clone(), + expires_in, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to insert payment methods session in db")?; + + let response = payment_methods::PaymentMethodsSessionResponse::foreign_from(( + payment_method_session_domain_model, + client_secret.secret, + )); + + Ok(services::ApplicationResponse::Json(response)) +} + +#[cfg(feature = "v2")] +pub async fn payment_methods_session_retrieve( + state: SessionState, + _merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + payment_method_session_id: id_type::GlobalPaymentMethodSessionId, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + let payment_method_session_domain_model = db + .get_payment_methods_session(key_manager_state, &key_store, &payment_method_session_id) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "payment methods session does not exist or has expired".to_string(), + }) + .attach_printable("Failed to retrieve payment methods session from db")?; + + let response = payment_methods::PaymentMethodsSessionResponse::foreign_from(( + payment_method_session_domain_model, + Secret::new("CLIENT_SECRET_REDACTED".to_string()), + )); + + Ok(services::ApplicationResponse::Json(response)) +} + +#[cfg(feature = "v2")] +pub async fn payment_methods_session_update_payment_method( + state: SessionState, + merchant_account: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + payment_method_session_id: id_type::GlobalPaymentMethodSessionId, + request: payment_methods::PaymentMethodSessionUpdateSavedPaymentMethod, +) -> RouterResponse { + let db = state.store.as_ref(); + let key_manager_state = &(&state).into(); + + // Validate if the session still exists + db.get_payment_methods_session(key_manager_state, &key_store, &payment_method_session_id) + .await + .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { + message: "payment methods session does not exist or has expired".to_string(), + }) + .attach_printable("Failed to retrieve payment methods session from db")?; + + let payment_method_update_request = request.payment_method_update_request; + + let updated_payment_method = update_payment_method_core( + state, + merchant_account, + key_store, + payment_method_update_request, + &request.payment_method_id, + ) + .await + .attach_printable("Failed to update saved payment method")?; + + Ok(services::ApplicationResponse::Json(updated_payment_method)) +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl pm_types::SavedPMLPaymentsInfo { pub async fn form_payments_info( diff --git a/crates/router/src/core/payment_methods/cards.rs b/crates/router/src/core/payment_methods/cards.rs index 1f4fb6904fd..1f37cc157e4 100644 --- a/crates/router/src/core/payment_methods/cards.rs +++ b/crates/router/src/core/payment_methods/cards.rs @@ -63,6 +63,7 @@ use strum::IntoEnumIterator; not(feature = "payment_methods_v2") ))] use super::migration; +#[cfg(feature = "v1")] use super::surcharge_decision_configs::{ perform_surcharge_decision_management_for_payment_method_list, perform_surcharge_decision_management_for_saved_cards, @@ -5434,10 +5435,7 @@ pub async fn get_masked_bank_details( .transpose()?; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] - let payment_method_data = pm - .payment_method_data - .clone() - .map(|x| x.into_inner().expose().into_inner()); + let payment_method_data = pm.payment_method_data.clone().map(|x| x.into_inner()); match payment_method_data { Some(pmd) => match pmd { diff --git a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs index 6a2b3198e93..7c8558ff8ca 100644 --- a/crates/router/src/core/payment_methods/surcharge_decision_configs.rs +++ b/crates/router/src/core/payment_methods/surcharge_decision_configs.rs @@ -367,82 +367,83 @@ pub async fn perform_surcharge_decision_management_for_saved_cards( Ok(surcharge_metadata) } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -pub async fn perform_surcharge_decision_management_for_saved_cards( - state: &SessionState, - algorithm_ref: routing::RoutingAlgorithmRef, - payment_attempt: &storage::PaymentAttempt, - payment_intent: &storage::PaymentIntent, - customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod], -) -> ConditionalConfigResult { - // let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.id.clone()); - let mut surcharge_metadata = todo!(); - - let surcharge_source = match ( - payment_attempt.get_surcharge_details(), - algorithm_ref.surcharge_config_algo_id, - ) { - (Some(request_surcharge_details), _) => { - SurchargeSource::Predetermined(request_surcharge_details) - } - (None, Some(algorithm_id)) => { - let cached_algo = ensure_algorithm_cached( - &*state.store, - &payment_attempt.merchant_id, - algorithm_id.as_str(), - ) - .await?; - - SurchargeSource::Generate(cached_algo) - } - (None, None) => return Ok(surcharge_metadata), - }; - let surcharge_source_log_message = match &surcharge_source { - SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules", - SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request", - }; - logger::debug!(customer_saved_card_list_surcharge_source = surcharge_source_log_message); - let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None) - .change_context(ConfigError::InputConstructionError)?; - - for customer_payment_method in customer_payment_method_list.iter_mut() { - let payment_token = customer_payment_method - .payment_token - .clone() - .get_required_value("payment_token") - .change_context(ConfigError::InputConstructionError)?; - - backend_input.payment_method.payment_method = - Some(customer_payment_method.payment_method_type); - backend_input.payment_method.payment_method_type = - customer_payment_method.payment_method_subtype; - - let card_network = match customer_payment_method.payment_method_data.as_ref() { - Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => { - card.card_network.clone() - } - _ => None, - }; - backend_input.payment_method.card_network = card_network; - - let surcharge_details = surcharge_source - .generate_surcharge_details_and_populate_surcharge_metadata( - &backend_input, - payment_attempt, - ( - &mut surcharge_metadata, - types::SurchargeKey::Token(payment_token), - ), - )?; - customer_payment_method.surcharge_details = surcharge_details - .map(|surcharge_details| { - SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt)) - .change_context(ConfigError::DslParsingError) - }) - .transpose()?; - } - Ok(surcharge_metadata) -} +// TODO: uncomment and resolve compiler error when required +// #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +// pub async fn perform_surcharge_decision_management_for_saved_cards( +// state: &SessionState, +// algorithm_ref: routing::RoutingAlgorithmRef, +// payment_attempt: &storage::PaymentAttempt, +// payment_intent: &storage::PaymentIntent, +// customer_payment_method_list: &mut [api_models::payment_methods::CustomerPaymentMethod], +// ) -> ConditionalConfigResult { +// // let mut surcharge_metadata = types::SurchargeMetadata::new(payment_attempt.id.clone()); +// let mut surcharge_metadata = todo!(); + +// let surcharge_source = match ( +// payment_attempt.get_surcharge_details(), +// algorithm_ref.surcharge_config_algo_id, +// ) { +// (Some(request_surcharge_details), _) => { +// SurchargeSource::Predetermined(request_surcharge_details) +// } +// (None, Some(algorithm_id)) => { +// let cached_algo = ensure_algorithm_cached( +// &*state.store, +// &payment_attempt.merchant_id, +// algorithm_id.as_str(), +// ) +// .await?; + +// SurchargeSource::Generate(cached_algo) +// } +// (None, None) => return Ok(surcharge_metadata), +// }; +// let surcharge_source_log_message = match &surcharge_source { +// SurchargeSource::Generate(_) => "Surcharge was calculated through surcharge rules", +// SurchargeSource::Predetermined(_) => "Surcharge was sent in payment create request", +// }; +// logger::debug!(customer_saved_card_list_surcharge_source = surcharge_source_log_message); +// let mut backend_input = make_dsl_input_for_surcharge(payment_attempt, payment_intent, None) +// .change_context(ConfigError::InputConstructionError)?; + +// for customer_payment_method in customer_payment_method_list.iter_mut() { +// let payment_token = customer_payment_method +// .payment_token +// .clone() +// .get_required_value("payment_token") +// .change_context(ConfigError::InputConstructionError)?; + +// backend_input.payment_method.payment_method = +// Some(customer_payment_method.payment_method_type); +// backend_input.payment_method.payment_method_type = +// customer_payment_method.payment_method_subtype; + +// let card_network = match customer_payment_method.payment_method_data.as_ref() { +// Some(api_models::payment_methods::PaymentMethodListData::Card(card)) => { +// card.card_network.clone() +// } +// _ => None, +// }; +// backend_input.payment_method.card_network = card_network; + +// let surcharge_details = surcharge_source +// .generate_surcharge_details_and_populate_surcharge_metadata( +// &backend_input, +// payment_attempt, +// ( +// &mut surcharge_metadata, +// types::SurchargeKey::Token(payment_token), +// ), +// )?; +// customer_payment_method.surcharge_details = surcharge_details +// .map(|surcharge_details| { +// SurchargeDetailsResponse::foreign_try_from((&surcharge_details, payment_attempt)) +// .change_context(ConfigError::DslParsingError) +// }) +// .transpose()?; +// } +// Ok(surcharge_metadata) +// } #[cfg(feature = "v2")] fn get_surcharge_details_from_surcharge_output( diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index 4a43eef1ed6..c4d3eafe463 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -16,7 +16,7 @@ use router_env::tracing_actix_web::RequestId; use serde::{Deserialize, Serialize}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use crate::types::payment_methods as pm_types; +use crate::types::{payment_methods as pm_types, transformers}; use crate::{ configs::settings, core::errors::{self, CustomResult}, @@ -551,12 +551,11 @@ pub fn generate_pm_vaulting_req_from_update_request( #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub fn generate_payment_method_response( pm: &domain::PaymentMethod, - ephemeral_key: Option>, ) -> errors::RouterResult { let pmd = pm .payment_method_data .clone() - .map(|data| data.into_inner().expose().into_inner()) + .map(|data| data.into_inner()) .and_then(|data| match data { api::PaymentMethodsData::Card(card) => { Some(api::PaymentMethodResponseData::Card(card.into())) @@ -573,7 +572,6 @@ pub fn generate_payment_method_response( created: Some(pm.created_at), recurring_enabled: false, last_used_at: Some(pm.last_used_at), - ephemeral_key, payment_method_data: pmd, }; @@ -911,3 +909,93 @@ pub fn mk_card_value2( .change_context(errors::VaultError::FetchCardFailed)?; Ok(value2_req) } + +#[cfg(feature = "v2")] +impl transformers::ForeignTryFrom for api::CustomerPaymentMethod { + type Error = error_stack::Report; + + fn foreign_try_from(item: domain::PaymentMethod) -> Result { + // For payment methods that are active we should always have the payment method subtype + let payment_method_subtype = + item.payment_method_subtype + .ok_or(errors::ValidationError::MissingRequiredField { + field_name: "payment_method_subtype".to_string(), + })?; + + // For payment methods that are active we should always have the payment method type + let payment_method_type = + item.payment_method_type + .ok_or(errors::ValidationError::MissingRequiredField { + field_name: "payment_method_type".to_string(), + })?; + + let payment_method_data = item + .payment_method_data + .map(|payment_method_data| payment_method_data.into_inner()) + .map(|payment_method_data| match payment_method_data { + api_models::payment_methods::PaymentMethodsData::Card( + card_details_payment_method, + ) => { + let card_details = api::CardDetailFromLocker::from(card_details_payment_method); + api_models::payment_methods::PaymentMethodListData::Card(card_details) + } + api_models::payment_methods::PaymentMethodsData::BankDetails(..) => todo!(), + api_models::payment_methods::PaymentMethodsData::WalletDetails(..) => { + todo!() + } + }); + + let payment_method_billing = item + .payment_method_billing_address + .clone() + .map(|billing| billing.into_inner()) + .map(From::from); + + // TODO: check how we can get this field + let recurring_enabled = true; + + Ok(Self { + id: item.id, + customer_id: item.customer_id, + payment_method_type, + payment_method_subtype, + created: item.created_at, + last_used_at: item.last_used_at, + recurring_enabled, + payment_method_data, + bank: None, + requires_cvv: true, + is_default: false, + billing: payment_method_billing, + }) + } +} + +#[cfg(feature = "v2")] +impl + transformers::ForeignFrom<( + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + Secret, + )> for api_models::payment_methods::PaymentMethodsSessionResponse +{ + fn foreign_from( + item: ( + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + Secret, + ), + ) -> Self { + let (session, client_secret) = item; + Self { + id: session.id, + customer_id: session.customer_id, + billing: session + .billing + .map(|address| address.into_inner()) + .map(From::from), + psp_tokenization: session.psp_tokenization, + network_tokenization: session.network_tokenization, + expires_at: session.expires_at, + client_secret, + } + } +} diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index cfa57a35654..28d843e2350 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, collections::HashSet, str::FromStr}; #[cfg(feature = "v2")] -use api_models::ephemeral_key::EphemeralKeyResponse; +use api_models::ephemeral_key::ClientSecretResponse; use api_models::{ mandates::RecurringDetails, payments::{additional_info as payment_additional_types, RequestSurchargeDetails}, @@ -3055,76 +3055,70 @@ pub async fn make_ephemeral_key( } #[cfg(feature = "v2")] -pub async fn make_ephemeral_key( +pub async fn make_client_secret( state: SessionState, - customer_id: id_type::GlobalCustomerId, + resource_id: api_models::ephemeral_key::ResourceId, merchant_account: domain::MerchantAccount, key_store: domain::MerchantKeyStore, headers: &actix_web::http::header::HeaderMap, -) -> errors::RouterResponse { +) -> errors::RouterResponse { let db = &state.store; let key_manager_state = &((&state).into()); - db.find_customer_by_global_id( - key_manager_state, - &customer_id, - merchant_account.get_id(), - &key_store, - merchant_account.storage_scheme, - ) - .await - .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; - - let resource_type = services::authentication::get_header_value_by_key( - headers::X_RESOURCE_TYPE.to_string(), - headers, - )? - .map(ephemeral_key::ResourceType::from_str) - .transpose() - .change_context(errors::ApiErrorResponse::InvalidRequestData { - message: format!("`{}` header is invalid", headers::X_RESOURCE_TYPE), - })? - .get_required_value("ResourceType") - .attach_printable("Failed to convert ResourceType from string")?; - - let ephemeral_key = create_ephemeral_key( - &state, - &customer_id, - merchant_account.get_id(), - resource_type, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to create ephemeral key")?; - let response = EphemeralKeyResponse::foreign_from(ephemeral_key); + match &resource_id { + api_models::ephemeral_key::ResourceId::Customer(global_customer_id) => { + db.find_customer_by_global_id( + key_manager_state, + global_customer_id, + merchant_account.get_id(), + &key_store, + merchant_account.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound)?; + } + } + + let resource_id = match resource_id { + api_models::ephemeral_key::ResourceId::Customer(global_customer_id) => { + common_utils::types::authentication::ResourceId::Customer(global_customer_id) + } + }; + + let client_secret = create_client_secret(&state, merchant_account.get_id(), resource_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to create client secret")?; + + let response = ClientSecretResponse::foreign_try_from(client_secret) + .attach_printable("Only customer is supported as resource_id in response")?; Ok(services::ApplicationResponse::Json(response)) } #[cfg(feature = "v2")] -pub async fn create_ephemeral_key( +pub async fn create_client_secret( state: &SessionState, - customer_id: &id_type::GlobalCustomerId, merchant_id: &id_type::MerchantId, - resource_type: ephemeral_key::ResourceType, -) -> RouterResult { + resource_id: common_utils::types::authentication::ResourceId, +) -> RouterResult { use common_utils::generate_time_ordered_id; let store = &state.store; - let id = id_type::EphemeralKeyId::generate(); - let secret = masking::Secret::new(generate_time_ordered_id("epk")); - let ephemeral_key = ephemeral_key::EphemeralKeyTypeNew { + let id = id_type::ClientSecretId::generate(); + let secret = masking::Secret::new(generate_time_ordered_id("cs")); + + let client_secret = ephemeral_key::ClientSecretTypeNew { id, - customer_id: customer_id.to_owned(), merchant_id: merchant_id.to_owned(), secret, - resource_type, + resource_id, }; - let ephemeral_key = store - .create_ephemeral_key(ephemeral_key, state.conf.eph_key.validity) + let client_secret = store + .create_client_secret(client_secret, state.conf.eph_key.validity) .await .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to create ephemeral key")?; - Ok(ephemeral_key) + .attach_printable("Unable to create client secret")?; + Ok(client_secret) } #[cfg(feature = "v1")] @@ -3142,13 +3136,13 @@ pub async fn delete_ephemeral_key( } #[cfg(feature = "v2")] -pub async fn delete_ephemeral_key( +pub async fn delete_client_secret( state: SessionState, ephemeral_key_id: String, -) -> errors::RouterResponse { +) -> errors::RouterResponse { let db = state.store.as_ref(); let ephemeral_key = db - .delete_ephemeral_key(&ephemeral_key_id) + .delete_client_secret(&ephemeral_key_id) .await .map_err(|err| match err.current_context() { errors::StorageError::ValueNotFound(_) => { @@ -3160,7 +3154,8 @@ pub async fn delete_ephemeral_key( }) .attach_printable("Unable to delete ephemeral key")?; - let response = EphemeralKeyResponse::foreign_from(ephemeral_key); + let response = ClientSecretResponse::foreign_try_from(ephemeral_key) + .attach_printable("Only customer is supported as resource_id in response")?; Ok(services::ApplicationResponse::Json(response)) } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5371afca931..1f6e03a0dec 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1929,7 +1929,7 @@ pub fn payments_to_payments_response( _connector_http_status_code: Option, _external_latency: Option, _is_latency_header_enabled: Option, -) -> RouterResponse +) -> RouterResponse where Op: Debug, D: OperationSessionGetters, @@ -2751,6 +2751,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay } } +#[cfg(feature = "v1")] impl ForeignFrom for api::ephemeral_key::EphemeralKeyCreateResponse { fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self { Self { diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 9b6396013f4..a08f80354bf 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -2,11 +2,9 @@ //! //! Functions that are used to perform the retrieval of merchant's //! routing dict, configs, defaults -use std::fmt::Debug; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use std::str::FromStr; -#[cfg(any(feature = "dynamic_routing", feature = "v1"))] -use std::sync::Arc; +use std::{fmt::Debug, sync::Arc}; use api_models::routing as routing_types; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index d852319e388..17da4902bac 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -30,6 +30,7 @@ pub mod merchant_key_store; pub mod organization; pub mod payment_link; pub mod payment_method; +pub mod payment_method_session; pub mod refund; pub mod relay; pub mod reverse_lookup; @@ -40,6 +41,7 @@ pub mod user; pub mod user_authentication_method; pub mod user_key_store; pub mod user_role; + use common_utils::id_type; use diesel_models::{ fraud_check::{FraudCheck, FraudCheckUpdate}, @@ -97,6 +99,7 @@ pub trait StorageInterface: + dashboard_metadata::DashboardMetadataInterface + dispute::DisputeInterface + ephemeral_key::EphemeralKeyInterface + + ephemeral_key::ClientSecretInterface + events::EventInterface + file::FileMetadataInterface + FraudCheckInterface @@ -135,6 +138,7 @@ pub trait StorageInterface: + generic_link::GenericLinkInterface + relay::RelayInterface + user::theme::ThemeInterface + + payment_method_session::PaymentMethodsSessionInterface + 'static { fn get_scheduler_db(&self) -> Box; diff --git a/crates/router/src/db/ephemeral_key.rs b/crates/router/src/db/ephemeral_key.rs index b3e33cd777f..2799d1c9ccb 100644 --- a/crates/router/src/db/ephemeral_key.rs +++ b/crates/router/src/db/ephemeral_key.rs @@ -3,7 +3,7 @@ use common_utils::id_type; use time::ext::NumericalDuration; #[cfg(feature = "v2")] -use crate::types::storage::ephemeral_key::{EphemeralKeyType, EphemeralKeyTypeNew, ResourceType}; +use crate::types::storage::ephemeral_key::{ClientSecretType, ClientSecretTypeNew}; use crate::{ core::errors::{self, CustomResult}, db::MockDb, @@ -19,36 +19,39 @@ pub trait EphemeralKeyInterface { _validity: i64, ) -> CustomResult; - #[cfg(feature = "v2")] - async fn create_ephemeral_key( - &self, - _ek: EphemeralKeyTypeNew, - _validity: i64, - ) -> CustomResult; - #[cfg(feature = "v1")] async fn get_ephemeral_key( &self, _key: &str, ) -> CustomResult; - #[cfg(feature = "v2")] - async fn get_ephemeral_key( - &self, - _key: &str, - ) -> CustomResult; - #[cfg(feature = "v1")] async fn delete_ephemeral_key( &self, _id: &str, ) -> CustomResult; +} +#[async_trait::async_trait] +pub trait ClientSecretInterface { #[cfg(feature = "v2")] - async fn delete_ephemeral_key( + async fn create_client_secret( + &self, + _ek: ClientSecretTypeNew, + _validity: i64, + ) -> CustomResult; + + #[cfg(feature = "v2")] + async fn get_client_secret( + &self, + _key: &str, + ) -> CustomResult; + + #[cfg(feature = "v2")] + async fn delete_client_secret( &self, _id: &str, - ) -> CustomResult; + ) -> CustomResult; } mod storage { @@ -65,11 +68,9 @@ mod storage { use storage_impl::redis::kv_store::RedisConnInterface; use time::ext::NumericalDuration; - use super::EphemeralKeyInterface; + use super::{ClientSecretInterface, EphemeralKeyInterface}; #[cfg(feature = "v2")] - use crate::types::storage::ephemeral_key::{ - EphemeralKeyType, EphemeralKeyTypeNew, ResourceType, - }; + use crate::types::storage::ephemeral_key::{ClientSecretType, ClientSecretTypeNew}; use crate::{ core::errors::{self, CustomResult}, services::Store, @@ -137,37 +138,74 @@ mod storage { } } + #[cfg(feature = "v1")] + #[instrument(skip_all)] + async fn get_ephemeral_key( + &self, + key: &str, + ) -> CustomResult { + let key = format!("epkey_{key}"); + self.get_redis_conn() + .map_err(Into::::into)? + .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKey") + .await + .change_context(errors::StorageError::KVError) + } + + #[cfg(feature = "v1")] + async fn delete_ephemeral_key( + &self, + id: &str, + ) -> CustomResult { + let ek = self.get_ephemeral_key(id).await?; + + self.get_redis_conn() + .map_err(Into::::into)? + .delete_key(&format!("epkey_{}", &ek.id).into()) + .await + .change_context(errors::StorageError::KVError)?; + + self.get_redis_conn() + .map_err(Into::::into)? + .delete_key(&format!("epkey_{}", &ek.secret).into()) + .await + .change_context(errors::StorageError::KVError)?; + Ok(ek) + } + } + + #[async_trait::async_trait] + impl ClientSecretInterface for Store { #[cfg(feature = "v2")] #[instrument(skip_all)] - async fn create_ephemeral_key( + async fn create_client_secret( &self, - new: EphemeralKeyTypeNew, + new: ClientSecretTypeNew, validity: i64, - ) -> CustomResult { + ) -> CustomResult { let created_at = date_time::now(); let expires = created_at.saturating_add(validity.hours()); let id_key = new.id.generate_redis_key(); - let created_ephemeral_key = EphemeralKeyType { + let created_client_secret = ClientSecretType { id: new.id, created_at, expires, - customer_id: new.customer_id, merchant_id: new.merchant_id, secret: new.secret, - resource_type: new.resource_type, + resource_id: new.resource_id, }; - let secret_key = created_ephemeral_key.generate_secret_key(); + let secret_key = created_client_secret.generate_secret_key(); match self .get_redis_conn() .map_err(Into::::into)? .serialize_and_set_multiple_hash_field_if_not_exist( &[ - (&secret_key.as_str().into(), &created_ephemeral_key), - (&id_key.as_str().into(), &created_ephemeral_key), + (&secret_key.as_str().into(), &created_client_secret), + (&id_key.as_str().into(), &created_client_secret), ], - "ephkey", + "csh", None, ) .await @@ -191,69 +229,34 @@ mod storage { .set_expire_at(&id_key.into(), expire_at) .await .change_context(errors::StorageError::KVError)?; - Ok(created_ephemeral_key) + Ok(created_client_secret) } Err(er) => Err(er).change_context(errors::StorageError::KVError), } } - #[cfg(feature = "v1")] - #[instrument(skip_all)] - async fn get_ephemeral_key( - &self, - key: &str, - ) -> CustomResult { - let key = format!("epkey_{key}"); - self.get_redis_conn() - .map_err(Into::::into)? - .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKey") - .await - .change_context(errors::StorageError::KVError) - } - #[cfg(feature = "v2")] #[instrument(skip_all)] - async fn get_ephemeral_key( + async fn get_client_secret( &self, key: &str, - ) -> CustomResult { - let key = format!("epkey_{key}"); + ) -> CustomResult { + let key = format!("cs_{key}"); self.get_redis_conn() .map_err(Into::::into)? - .get_hash_field_and_deserialize(&key.into(), "ephkey", "EphemeralKeyType") + .get_hash_field_and_deserialize(&key.into(), "csh", "ClientSecretType") .await .change_context(errors::StorageError::KVError) } - #[cfg(feature = "v1")] - async fn delete_ephemeral_key( - &self, - id: &str, - ) -> CustomResult { - let ek = self.get_ephemeral_key(id).await?; - - self.get_redis_conn() - .map_err(Into::::into)? - .delete_key(&format!("epkey_{}", &ek.id).into()) - .await - .change_context(errors::StorageError::KVError)?; - - self.get_redis_conn() - .map_err(Into::::into)? - .delete_key(&format!("epkey_{}", &ek.secret).into()) - .await - .change_context(errors::StorageError::KVError)?; - Ok(ek) - } - #[cfg(feature = "v2")] - async fn delete_ephemeral_key( + async fn delete_client_secret( &self, id: &str, - ) -> CustomResult { - let ephemeral_key = self.get_ephemeral_key(id).await?; - let redis_id_key = ephemeral_key.id.generate_redis_key(); - let secret_key = ephemeral_key.generate_secret_key(); + ) -> CustomResult { + let client_secret = self.get_client_secret(id).await?; + let redis_id_key = client_secret.id.generate_redis_key(); + let secret_key = client_secret.generate_secret_key(); self.get_redis_conn() .map_err(Into::::into)? @@ -276,7 +279,7 @@ mod storage { } _ => err.change_context(errors::StorageError::KVError), })?; - Ok(ephemeral_key) + Ok(client_secret) } } } @@ -305,15 +308,6 @@ impl EphemeralKeyInterface for MockDb { Ok(ephemeral_key) } - #[cfg(feature = "v2")] - async fn create_ephemeral_key( - &self, - ek: EphemeralKeyTypeNew, - validity: i64, - ) -> CustomResult { - todo!() - } - #[cfg(feature = "v1")] async fn get_ephemeral_key( &self, @@ -333,14 +327,6 @@ impl EphemeralKeyInterface for MockDb { } } - #[cfg(feature = "v2")] - async fn get_ephemeral_key( - &self, - key: &str, - ) -> CustomResult { - todo!() - } - #[cfg(feature = "v1")] async fn delete_ephemeral_key( &self, @@ -356,12 +342,32 @@ impl EphemeralKeyInterface for MockDb { ); } } +} +#[async_trait::async_trait] +impl ClientSecretInterface for MockDb { #[cfg(feature = "v2")] - async fn delete_ephemeral_key( + async fn create_client_secret( + &self, + new: ClientSecretTypeNew, + validity: i64, + ) -> CustomResult { + todo!() + } + + #[cfg(feature = "v2")] + async fn get_client_secret( + &self, + key: &str, + ) -> CustomResult { + todo!() + } + + #[cfg(feature = "v2")] + async fn delete_client_secret( &self, id: &str, - ) -> CustomResult { + ) -> CustomResult { todo!() } } diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 3622695c9f2..2dc7191863a 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -7,7 +7,7 @@ use common_utils::{ types::{keymanager::KeyManagerState, theme::ThemeLineage}, }; #[cfg(feature = "v2")] -use diesel_models::ephemeral_key::{EphemeralKeyType, EphemeralKeyTypeNew}; +use diesel_models::ephemeral_key::{ClientSecretType, ClientSecretTypeNew}; use diesel_models::{ enums, enums::ProcessTrackerStatus, @@ -39,6 +39,7 @@ use time::PrimitiveDateTime; use super::{ dashboard_metadata::DashboardMetadataInterface, + ephemeral_key::ClientSecretInterface, role::RoleInterface, user::{sample_data::BatchSampleDataInterface, theme::ThemeInterface, UserInterface}, user_authentication_method::UserAuthenticationMethodInterface, @@ -50,6 +51,7 @@ use crate::services::kafka::payout::KafkaPayout; use crate::{ core::errors::{self, ProcessTrackerError}, db::{ + self, address::AddressInterface, api_keys::ApiKeyInterface, authentication::AuthenticationInterface, @@ -658,15 +660,6 @@ impl EphemeralKeyInterface for KafkaStore { self.diesel_store.create_ephemeral_key(ek, validity).await } - #[cfg(feature = "v2")] - async fn create_ephemeral_key( - &self, - ek: EphemeralKeyTypeNew, - validity: i64, - ) -> CustomResult { - self.diesel_store.create_ephemeral_key(ek, validity).await - } - #[cfg(feature = "v1")] async fn get_ephemeral_key( &self, @@ -675,14 +668,6 @@ impl EphemeralKeyInterface for KafkaStore { self.diesel_store.get_ephemeral_key(key).await } - #[cfg(feature = "v2")] - async fn get_ephemeral_key( - &self, - key: &str, - ) -> CustomResult { - self.diesel_store.get_ephemeral_key(key).await - } - #[cfg(feature = "v1")] async fn delete_ephemeral_key( &self, @@ -690,13 +675,33 @@ impl EphemeralKeyInterface for KafkaStore { ) -> CustomResult { self.diesel_store.delete_ephemeral_key(id).await } +} +#[async_trait::async_trait] +impl ClientSecretInterface for KafkaStore { #[cfg(feature = "v2")] - async fn delete_ephemeral_key( + async fn create_client_secret( + &self, + ek: ClientSecretTypeNew, + validity: i64, + ) -> CustomResult { + self.diesel_store.create_client_secret(ek, validity).await + } + + #[cfg(feature = "v2")] + async fn get_client_secret( + &self, + key: &str, + ) -> CustomResult { + self.diesel_store.get_client_secret(key).await + } + + #[cfg(feature = "v2")] + async fn delete_client_secret( &self, id: &str, - ) -> CustomResult { - self.diesel_store.delete_ephemeral_key(id).await + ) -> CustomResult { + self.diesel_store.delete_client_secret(id).await } } @@ -3922,6 +3927,40 @@ impl ThemeInterface for KafkaStore { } } +#[async_trait::async_trait] +#[cfg(feature = "v2")] +impl db::payment_method_session::PaymentMethodsSessionInterface for KafkaStore { + async fn insert_payment_methods_session( + &self, + state: &KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + payment_methods_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + validity: i64, + ) -> CustomResult<(), errors::StorageError> { + self.diesel_store + .insert_payment_methods_session(state, key_store, payment_methods_session, validity) + .await + } + + async fn get_payment_methods_session( + &self, + state: &KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + id: &id_type::GlobalPaymentMethodSessionId, + ) -> CustomResult< + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + errors::StorageError, + > { + self.diesel_store + .get_payment_methods_session(state, key_store, id) + .await + } +} + +#[async_trait::async_trait] +#[cfg(feature = "v1")] +impl db::payment_method_session::PaymentMethodsSessionInterface for KafkaStore {} + #[async_trait::async_trait] impl CallbackMapperInterface for KafkaStore { #[instrument(skip_all)] diff --git a/crates/router/src/db/payment_method_session.rs b/crates/router/src/db/payment_method_session.rs new file mode 100644 index 00000000000..a7e500ac52c --- /dev/null +++ b/crates/router/src/db/payment_method_session.rs @@ -0,0 +1,137 @@ +#[cfg(feature = "v2")] +use crate::core::errors::{self, CustomResult}; +use crate::db::MockDb; + +#[cfg(feature = "v2")] +#[async_trait::async_trait] +pub trait PaymentMethodsSessionInterface { + async fn insert_payment_methods_session( + &self, + state: &common_utils::types::keymanager::KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + payment_methods_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + validity: i64, + ) -> CustomResult<(), errors::StorageError>; + + async fn get_payment_methods_session( + &self, + state: &common_utils::types::keymanager::KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + id: &common_utils::id_type::GlobalPaymentMethodSessionId, + ) -> CustomResult< + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + errors::StorageError, + >; +} + +#[cfg(feature = "v1")] +pub trait PaymentMethodsSessionInterface {} + +#[cfg(feature = "v1")] +impl PaymentMethodsSessionInterface for crate::services::Store {} + +#[cfg(feature = "v2")] +mod storage { + use error_stack::ResultExt; + use hyperswitch_domain_models::behaviour::{Conversion, ReverseConversion}; + use router_env::{instrument, tracing}; + use storage_impl::redis::kv_store::RedisConnInterface; + + use super::PaymentMethodsSessionInterface; + use crate::{ + core::errors::{self, CustomResult}, + services::Store, + }; + + #[async_trait::async_trait] + impl PaymentMethodsSessionInterface for Store { + #[instrument(skip_all)] + async fn insert_payment_methods_session( + &self, + _state: &common_utils::types::keymanager::KeyManagerState, + _key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + payment_methods_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + validity_in_seconds: i64, + ) -> CustomResult<(), errors::StorageError> { + let redis_key = payment_methods_session.id.get_redis_key(); + + let db_model = payment_methods_session + .construct_new() + .await + .change_context(errors::StorageError::EncryptionError)?; + + let redis_connection = self + .get_redis_conn() + .map_err(Into::::into)?; + + redis_connection + .serialize_and_set_key_with_expiry(&redis_key.into(), db_model, validity_in_seconds) + .await + .change_context(errors::StorageError::KVError) + .attach_printable("Failed to insert payment methods session to redis") + } + + #[instrument(skip_all)] + async fn get_payment_methods_session( + &self, + state: &common_utils::types::keymanager::KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + id: &common_utils::id_type::GlobalPaymentMethodSessionId, + ) -> CustomResult< + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + errors::StorageError, + > { + let redis_key = id.get_redis_key(); + + let redis_connection = self + .get_redis_conn() + .map_err(Into::::into)?; + + let db_model = redis_connection + .get_and_deserialize_key::(&redis_key.into(), "PaymentMethodsSession") + .await + .change_context(errors::StorageError::KVError)?; + + let key_manager_identifier = common_utils::types::keymanager::Identifier::Merchant( + key_store.merchant_id.clone(), + ); + + db_model + .convert(state, &key_store.key, key_manager_identifier) + .await + .change_context(errors::StorageError::DecryptionError) + .attach_printable("Failed to decrypt payment methods session") + } + } +} + +#[cfg(feature = "v2")] +#[async_trait::async_trait] +impl PaymentMethodsSessionInterface for MockDb { + async fn insert_payment_methods_session( + &self, + state: &common_utils::types::keymanager::KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + payment_methods_session: hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + validity_in_seconds: i64, + ) -> CustomResult<(), errors::StorageError> { + Err(errors::StorageError::MockDbError)? + } + + #[cfg(feature = "v2")] + async fn get_payment_methods_session( + &self, + state: &common_utils::types::keymanager::KeyManagerState, + key_store: &hyperswitch_domain_models::merchant_key_store::MerchantKeyStore, + id: &common_utils::id_type::GlobalPaymentMethodSessionId, + ) -> CustomResult< + hyperswitch_domain_models::payment_methods::PaymentMethodsSession, + errors::StorageError, + > { + Err(errors::StorageError::MockDbError)? + } +} + +#[cfg(feature = "v1")] +#[async_trait::async_trait] +impl PaymentMethodsSessionInterface for MockDb {} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 9febfa5e469..9b43d7f2b16 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -90,8 +90,8 @@ pub mod headers { pub const X_REDIRECT_URI: &str = "x-redirect-uri"; pub const X_TENANT_ID: &str = "x-tenant-id"; pub const X_CLIENT_SECRET: &str = "X-Client-Secret"; + pub const X_CUSTOMER_ID: &str = "X-Customer-Id"; pub const X_CONNECTED_MERCHANT_ID: &str = "x-connected-merchant-id"; - pub const X_RESOURCE_TYPE: &str = "X-Resource-Type"; } pub mod pii { @@ -151,6 +151,11 @@ pub fn mk_app( server_app = server_app.service(routes::PaymentMethods::server(state.clone())); } + #[cfg(all(feature = "v2", feature = "oltp"))] + { + server_app = server_app.service(routes::PaymentMethodsSession::server(state.clone())); + } + #[cfg(feature = "v1")] { server_app = server_app diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index 22f5983e4c3..80906570d7c 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -63,6 +63,8 @@ pub mod relay; #[cfg(feature = "dummy_connector")] pub use self::app::DummyConnector; +#[cfg(feature = "v2")] +pub use self::app::PaymentMethodsSession; #[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] pub use self::app::Recon; pub use self::app::{ diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index e4da0119fdd..c5e1f8a1082 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -1016,13 +1016,6 @@ impl Customers { .route(web::delete().to(customers::customers_delete)), ) } - #[cfg(all(feature = "oltp", feature = "v2", feature = "payment_methods_v2"))] - { - route = route.service( - web::resource("/{customer_id}/saved-payment-methods") - .route(web::get().to(payment_methods::list_customer_payment_method_api)), - ); - } route } } @@ -1182,14 +1175,9 @@ impl PaymentMethods { .route(web::get().to(payment_methods::payment_method_retrieve_api)) .route(web::delete().to(payment_methods::payment_method_delete_api)), ) - .service( - web::resource("/list-enabled-payment-methods") - .route(web::get().to(payment_methods::list_payment_methods_enabled)), - ) - .service( - web::resource("/confirm-intent") - .route(web::post().to(payment_methods::confirm_payment_method_intent_api)), - ) + .service(web::resource("/list-enabled-payment-methods").route( + web::get().to(payment_methods::payment_method_session_list_payment_methods), + )) .service( web::resource("/update-saved-payment-method") .route(web::put().to(payment_methods::payment_method_update_api)), @@ -1266,6 +1254,40 @@ impl PaymentMethods { } } +#[cfg(all(feature = "v2", feature = "oltp"))] +pub struct PaymentMethodsSession; + +#[cfg(all(feature = "v2", feature = "oltp"))] +impl PaymentMethodsSession { + pub fn server(state: AppState) -> Scope { + let mut route = web::scope("/v2/payment-methods-session").app_data(web::Data::new(state)); + route = route.service( + web::resource("") + .route(web::post().to(payment_methods::payment_methods_session_create)), + ); + + route = route.service( + web::scope("/{payment_method_session_id}") + .service( + web::resource("") + .route(web::get().to(payment_methods::payment_methods_session_retrieve)), + ) + .service(web::resource("/list-payment-methods").route( + web::get().to(payment_methods::payment_method_session_list_payment_methods), + )) + .service( + web::resource("/update-saved-payment-method").route( + web::put().to( + payment_methods::payment_method_session_update_saved_payment_method, + ), + ), + ), + ); + + route + } +} + #[cfg(all(feature = "olap", feature = "recon", feature = "v1"))] pub struct Recon; @@ -1483,10 +1505,10 @@ impl EphemeralKey { #[cfg(feature = "v2")] impl EphemeralKey { pub fn server(config: AppState) -> Scope { - web::scope("/v2/ephemeral-keys") + web::scope("/v2/client-secret") .app_data(web::Data::new(config)) - .service(web::resource("").route(web::post().to(ephemeral_key_create))) - .service(web::resource("/{id}").route(web::delete().to(ephemeral_key_delete))) + .service(web::resource("").route(web::post().to(client_secret_create))) + .service(web::resource("/{id}").route(web::delete().to(client_secret_delete))) } } diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index 09b6983d2a7..20f53c17aa0 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -85,19 +85,21 @@ pub async fn customers_retrieve( req: HttpRequest, path: web::Path, ) -> HttpResponse { + use crate::services::authentication::api_or_client_auth; + let flow = Flow::CustomersRetrieve; let id = path.into_inner(); + let v2_client_auth = auth::V2ClientAuth( + common_utils::types::authentication::ResourceId::Customer(id.clone()), + ); let auth = if auth::is_jwt_auth(req.headers()) { - Box::new(auth::JWTAuth { + &auth::JWTAuth { permission: Permission::MerchantCustomerRead, - }) - } else { - match auth::is_ephemeral_auth(req.headers()) { - Ok(auth) => auth, - Err(err) => return api::log_and_return_error_response(err), } + } else { + api_or_client_auth(&auth::V2ApiKeyAuth, &v2_client_auth, req.headers()) }; Box::pin(api::server_wrap( @@ -108,7 +110,7 @@ pub async fn customers_retrieve( |state, auth: auth::AuthenticationData, id, _| { retrieve_customer(state, auth.merchant_account, auth.key_store, id) }, - &*auth, + auth, api_locking::LockAction::NotApplicable, )) .await diff --git a/crates/router/src/routes/ephemeral_key.rs b/crates/router/src/routes/ephemeral_key.rs index 0330482e81a..92b5d667daa 100644 --- a/crates/router/src/routes/ephemeral_key.rs +++ b/crates/router/src/routes/ephemeral_key.rs @@ -7,7 +7,7 @@ use crate::{ services::{api, authentication as auth}, }; -#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +#[cfg(all(feature = "v1", not(feature = "customer_v2")))] #[instrument(skip_all, fields(flow = ?Flow::EphemeralKeyCreate))] pub async fn ephemeral_key_create( state: web::Data, @@ -34,12 +34,33 @@ pub async fn ephemeral_key_create( .await } +#[cfg(feature = "v1")] +#[instrument(skip_all, fields(flow = ?Flow::EphemeralKeyDelete))] +pub async fn ephemeral_key_delete( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::EphemeralKeyDelete; + let payload = path.into_inner(); + api::server_wrap( + flow, + state, + &req, + payload, + |state, _: auth::AuthenticationData, req, _| helpers::delete_ephemeral_key(state, req), + &auth::HeaderAuth(auth::ApiKeyAuth), + api_locking::LockAction::NotApplicable, + ) + .await +} + #[cfg(feature = "v2")] #[instrument(skip_all, fields(flow = ?Flow::EphemeralKeyCreate))] -pub async fn ephemeral_key_create( +pub async fn client_secret_create( state: web::Data, req: HttpRequest, - json_payload: web::Json, + json_payload: web::Json, ) -> HttpResponse { let flow = Flow::EphemeralKeyCreate; let payload = json_payload.into_inner(); @@ -49,9 +70,9 @@ pub async fn ephemeral_key_create( &req, payload, |state, auth: auth::AuthenticationData, payload, _| { - helpers::make_ephemeral_key( + helpers::make_client_secret( state, - payload.customer_id.to_owned(), + payload.resource_id.to_owned(), auth.merchant_account, auth.key_store, req.headers(), @@ -63,8 +84,9 @@ pub async fn ephemeral_key_create( .await } +#[cfg(feature = "v2")] #[instrument(skip_all, fields(flow = ?Flow::EphemeralKeyDelete))] -pub async fn ephemeral_key_delete( +pub async fn client_secret_delete( state: web::Data, req: HttpRequest, path: web::Path, @@ -76,7 +98,7 @@ pub async fn ephemeral_key_delete( state, &req, payload, - |state, _: auth::AuthenticationData, req, _| helpers::delete_ephemeral_key(state, req), + |state, _: auth::AuthenticationData, req, _| helpers::delete_client_secret(state, req), &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, ) diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index b09f92364bc..ce9dda97c9f 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -39,6 +39,7 @@ pub enum ApiIdentifier { ApplePayCertificatesMigration, Relay, Documentation, + PaymentMethodsSession, } impl From for ApiIdentifier { @@ -309,6 +310,10 @@ impl From for ApiIdentifier { Flow::RetrievePollStatus => Self::Poll, Flow::FeatureMatrix => Self::Documentation, + + Flow::PaymentMethodSessionCreate + | Flow::PaymentMethodSessionRetrieve + | Flow::PaymentMethodSessionUpdateSavedPaymentMethod => Self::PaymentMethodsSession, } } } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 5f9fe729eeb..86f1874ab09 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -11,12 +11,6 @@ use hyperswitch_domain_models::merchant_key_store::MerchantKeyStore; use router_env::{instrument, logger, tracing, Flow}; use super::app::{AppState, SessionState}; -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use crate::core::payment_methods::{ - create_payment_method, delete_payment_method, list_customer_payment_method_util, - payment_method_intent_confirm, payment_method_intent_create, retrieve_payment_method, - update_payment_method, -}; use crate::{ core::{ api_locking, @@ -86,7 +80,7 @@ pub async fn create_payment_method_api( &req, json_payload.into_inner(), |state, auth: auth::AuthenticationData, req, _| async move { - Box::pin(create_payment_method( + Box::pin(payment_methods_routes::create_payment_method( &state, req, &auth.merchant_account, @@ -115,7 +109,7 @@ pub async fn create_payment_method_intent_api( &req, json_payload.into_inner(), |state, auth: auth::AuthenticationData, req, _| async move { - Box::pin(payment_method_intent_create( + Box::pin(payment_methods_routes::payment_method_intent_create( &state, req, &auth.merchant_account, @@ -123,7 +117,7 @@ pub async fn create_payment_method_intent_api( )) .await }, - &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::V2ApiKeyAuth, api_locking::LockAction::NotApplicable, )) .await @@ -134,21 +128,13 @@ pub async fn create_payment_method_intent_api( #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub struct PaymentMethodIntentConfirmInternal { pub id: id_type::GlobalPaymentMethodId, - pub payment_method_type: common_enums::PaymentMethod, - pub payment_method_subtype: common_enums::PaymentMethodType, - pub customer_id: Option, - pub payment_method_data: payment_methods::PaymentMethodCreateData, + pub request: payment_methods::PaymentMethodIntentConfirm, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] impl From for payment_methods::PaymentMethodIntentConfirm { fn from(item: PaymentMethodIntentConfirmInternal) -> Self { - Self { - payment_method_type: item.payment_method_type, - payment_method_subtype: item.payment_method_subtype, - customer_id: item.customer_id, - payment_method_data: item.payment_method_data.clone(), - } + item.request } } @@ -157,128 +143,39 @@ impl common_utils::events::ApiEventMetric for PaymentMethodIntentConfirmInternal fn get_api_event_type(&self) -> Option { Some(common_utils::events::ApiEventsType::PaymentMethod { payment_method_id: self.id.clone(), - payment_method_type: Some(self.payment_method_type), - payment_method_subtype: Some(self.payment_method_subtype), + payment_method_type: Some(self.request.payment_method_type), + payment_method_subtype: Some(self.request.payment_method_subtype), }) } } -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsCreate))] -pub async fn confirm_payment_method_intent_api( - state: web::Data, - req: HttpRequest, - json_payload: web::Json, - path: web::Path, -) -> HttpResponse { - let flow = Flow::PaymentMethodsCreate; - let pm_id = path.into_inner(); - let payload = json_payload.into_inner(); - - let auth = match auth::is_ephemeral_or_publishible_auth(req.headers()) { - Ok(auth) => auth, - Err(e) => return api::log_and_return_error_response(e), - }; - - let inner_payload = PaymentMethodIntentConfirmInternal { - id: pm_id.to_owned(), - payment_method_type: payload.payment_method_type, - payment_method_subtype: payload.payment_method_subtype, - customer_id: payload.customer_id.to_owned(), - payment_method_data: payload.payment_method_data.clone(), - }; - - Box::pin(api::server_wrap( - flow, - state, - &req, - inner_payload, - |state, auth: auth::AuthenticationData, req, _| { - let pm_id = pm_id.clone(); - async move { - Box::pin(payment_method_intent_confirm( - &state, - req.into(), - &auth.merchant_account, - &auth.key_store, - pm_id, - )) - .await - } - }, - &*auth, - api_locking::LockAction::NotApplicable, - )) - .await -} - -#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsList))] -pub async fn list_payment_methods_enabled( - state: web::Data, - req: HttpRequest, - path: web::Path, -) -> HttpResponse { - let flow = Flow::PaymentMethodsList; - let payment_method_id = path.into_inner(); - - let auth = match auth::is_ephemeral_or_publishible_auth(req.headers()) { - Ok(auth) => auth, - Err(e) => return api::log_and_return_error_response(e), - }; - - Box::pin(api::server_wrap( - flow, - state, - &req, - payment_method_id, - |state, auth: auth::AuthenticationData, payment_method_id, _| { - payment_methods_routes::list_payment_methods_enabled( - state, - auth.merchant_account, - auth.key_store, - auth.profile, - payment_method_id, - ) - }, - &*auth, - api_locking::LockAction::NotApplicable, - )) - .await -} - #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsUpdate))] pub async fn payment_method_update_api( state: web::Data, req: HttpRequest, - path: web::Path, + path: web::Path, json_payload: web::Json, ) -> HttpResponse { let flow = Flow::PaymentMethodsUpdate; let payment_method_id = path.into_inner(); let payload = json_payload.into_inner(); - let auth = match auth::is_ephemeral_or_publishible_auth(req.headers()) { - Ok(auth) => auth, - Err(e) => return api::log_and_return_error_response(e), - }; - Box::pin(api::server_wrap( flow, state, &req, payload, |state, auth: auth::AuthenticationData, req, _| { - update_payment_method( + payment_methods_routes::update_payment_method( state, auth.merchant_account, + auth.key_store, req, &payment_method_id, - auth.key_store, ) }, - &*auth, + &auth::V2ApiKeyAuth, api_locking::LockAction::NotApplicable, )) .await @@ -303,7 +200,12 @@ pub async fn payment_method_retrieve_api( &req, payload, |state, auth: auth::AuthenticationData, pm, _| { - retrieve_payment_method(state, pm, auth.key_store, auth.merchant_account) + payment_methods_routes::retrieve_payment_method( + state, + pm, + auth.key_store, + auth.merchant_account, + ) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -330,7 +232,12 @@ pub async fn payment_method_delete_api( &req, payload, |state, auth: auth::AuthenticationData, pm, _| { - delete_payment_method(state, pm, auth.key_store, auth.merchant_account) + payment_methods_routes::delete_payment_method( + state, + pm, + auth.key_store, + auth.merchant_account, + ) }, &auth::HeaderAuth(auth::ApiKeyAuth), api_locking::LockAction::NotApplicable, @@ -580,49 +487,6 @@ pub async fn list_customer_payment_method_api( .await } -#[cfg(all( - feature = "v2", - feature = "payment_methods_v2", - feature = "customer_v2" -))] -#[instrument(skip_all, fields(flow = ?Flow::CustomerPaymentMethodsList))] -pub async fn list_customer_payment_method_api( - state: web::Data, - customer_id: web::Path, - req: HttpRequest, - query_payload: web::Query, -) -> HttpResponse { - let flow = Flow::CustomerPaymentMethodsList; - let payload = query_payload.into_inner(); - let customer_id = customer_id.into_inner(); - - let ephemeral_or_api_auth = match auth::is_ephemeral_auth(req.headers()) { - Ok(auth) => auth, - Err(err) => return api::log_and_return_error_response(err), - }; - - Box::pin(api::server_wrap( - flow, - state, - &req, - payload, - |state, auth: auth::AuthenticationData, req, _| { - list_customer_payment_method_util( - state, - auth.merchant_account, - auth.profile, - auth.key_store, - Some(req), - Some(customer_id.clone()), - false, - ) - }, - &*ephemeral_or_api_auth, - api_locking::LockAction::NotApplicable, - )) - .await -} - #[cfg(all( any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2"), @@ -1022,3 +886,167 @@ impl ParentPaymentMethodToken { } } } + +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSessionCreate))] +pub async fn payment_methods_session_create( + state: web::Data, + req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::PaymentMethodSessionCreate; + let payload = json_payload.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, request, _| async move { + payment_methods_routes::payment_methods_session_create( + state, + auth.merchant_account, + auth.key_store, + request, + ) + .await + }, + &auth::V2ApiKeyAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "v2")] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSessionRetrieve))] +pub async fn payment_methods_session_retrieve( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::PaymentMethodSessionRetrieve; + let payment_method_session_id = path.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payment_method_session_id.clone(), + |state, auth: auth::AuthenticationData, payment_method_session_id, _| async move { + payment_methods_routes::payment_methods_session_retrieve( + state, + auth.merchant_account, + auth.key_store, + payment_method_session_id, + ) + .await + }, + auth::api_or_client_auth( + &auth::V2ApiKeyAuth, + &auth::V2ClientAuth( + common_utils::types::authentication::ResourceId::PaymentMethodSession( + payment_method_session_id, + ), + ), + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodsList))] +pub async fn payment_method_session_list_payment_methods( + state: web::Data, + req: HttpRequest, + path: web::Path, +) -> HttpResponse { + let flow = Flow::PaymentMethodsList; + let payment_method_session_id = path.into_inner(); + + Box::pin(api::server_wrap( + flow, + state, + &req, + payment_method_session_id.clone(), + |state, auth: auth::AuthenticationData, payment_method_session_id, _| { + payment_methods_routes::list_payment_methods_for_session( + state, + auth.merchant_account, + auth.key_store, + auth.profile, + payment_method_session_id, + ) + }, + &auth::V2ClientAuth( + common_utils::types::authentication::ResourceId::PaymentMethodSession( + payment_method_session_id, + ), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize)] +struct PaymentMethodsSessionGenericRequest { + payment_method_session_id: id_type::GlobalPaymentMethodSessionId, + #[serde(flatten)] + request: T, +} + +#[cfg(feature = "v2")] +impl common_utils::events::ApiEventMetric + for PaymentMethodsSessionGenericRequest +{ + fn get_api_event_type(&self) -> Option { + Some(common_utils::events::ApiEventsType::PaymentMethodSession { + payment_method_session_id: self.payment_method_session_id.clone(), + }) + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[instrument(skip_all, fields(flow = ?Flow::PaymentMethodSessionUpdateSavedPaymentMethod))] +pub async fn payment_method_session_update_saved_payment_method( + state: web::Data, + req: HttpRequest, + path: web::Path, + json_payload: web::Json< + api_models::payment_methods::PaymentMethodSessionUpdateSavedPaymentMethod, + >, +) -> HttpResponse { + let flow = Flow::PaymentMethodSessionUpdateSavedPaymentMethod; + let payload = json_payload.into_inner(); + let payment_method_session_id = path.into_inner(); + + let request = PaymentMethodsSessionGenericRequest { + payment_method_session_id: payment_method_session_id.clone(), + request: payload, + }; + + Box::pin(api::server_wrap( + flow, + state, + &req, + request, + |state, auth: auth::AuthenticationData, request, _| { + payment_methods_routes::payment_methods_session_update_payment_method( + state, + auth.merchant_account, + auth.key_store, + request.payment_method_session_id, + request.request, + ) + }, + &auth::V2ClientAuth( + common_utils::types::authentication::ResourceId::PaymentMethodSession( + payment_method_session_id, + ), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index 99800b55512..edf127ac30f 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -20,6 +20,8 @@ use common_utils::{date_time, id_type}; use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation}; +#[cfg(feature = "v2")] +use masking::ExposeInterface; use masking::PeekInterface; use router_env::logger; use serde::Serialize; @@ -1286,6 +1288,17 @@ impl<'a> HeaderMapStruct<'a> { }) } + pub fn get_auth_string_from_header(&self) -> RouterResult<&str> { + self.headers + .get(headers::AUTHORIZATION) + .get_required_value(headers::AUTHORIZATION)? + .to_str() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: headers::AUTHORIZATION, + }) + .attach_printable("Failed to convert authorization header to string") + } + pub fn get_id_type_from_header_if_present(&self, key: &str) -> RouterResult> where T: TryFrom< @@ -1510,44 +1523,6 @@ where } } -#[cfg(feature = "v2")] -#[async_trait] -impl AuthenticateAndFetch for EphemeralKeyAuth -where - A: SessionStateInfo + Sync, -{ - async fn authenticate_and_fetch( - &self, - request_headers: &HeaderMap, - state: &A, - ) -> RouterResult<(AuthenticationData, AuthenticationType)> { - let api_key = - get_api_key(request_headers).change_context(errors::ApiErrorResponse::Unauthorized)?; - let ephemeral_key = state - .store() - .get_ephemeral_key(api_key) - .await - .change_context(errors::ApiErrorResponse::Unauthorized)?; - - let resource_type = HeaderMapStruct::new(request_headers) - .get_mandatory_header_value_by_key(headers::X_RESOURCE_TYPE) - .and_then(|val| { - ephemeral_key::ResourceType::from_str(val).change_context( - errors::ApiErrorResponse::InvalidRequestData { - message: format!("`{}` header is invalid", headers::X_RESOURCE_TYPE), - }, - ) - })?; - - fp_utils::when(resource_type != ephemeral_key.resource_type, || { - Err(errors::ApiErrorResponse::Unauthorized) - })?; - - MerchantIdAuth(ephemeral_key.merchant_id) - .authenticate_and_fetch(request_headers, state) - .await - } -} #[derive(Debug)] pub struct MerchantIdAuth(pub id_type::MerchantId); @@ -1778,6 +1753,249 @@ where } } +/// Take api-key from `Authorization` header +#[cfg(feature = "v2")] +#[derive(Debug)] +pub struct V2ApiKeyAuth; + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for V2ApiKeyAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let header_map_struct = HeaderMapStruct::new(request_headers); + let auth_string = header_map_struct.get_auth_string_from_header()?; + + let api_key = auth_string + .split(',') + .find_map(|part| part.trim().strip_prefix("api-key=")) + .ok_or_else(|| { + report!(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Unable to parse api_key") + })?; + if api_key.is_empty() { + return Err(errors::ApiErrorResponse::Unauthorized) + .attach_printable("API key is empty"); + } + + let profile_id = HeaderMapStruct::new(request_headers) + .get_id_type_from_header::(headers::X_PROFILE_ID)?; + + let api_key = api_keys::PlaintextApiKey::from(api_key); + let hash_key = { + let config = state.conf(); + config.api_keys.get_inner().get_hash_key()? + }; + let hashed_api_key = api_key.keyed_hash(hash_key.peek()); + + let stored_api_key = state + .store() + .find_api_key_by_hash_optional(hashed_api_key.into()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) // If retrieve failed + .attach_printable("Failed to retrieve API key")? + .ok_or(report!(errors::ApiErrorResponse::Unauthorized)) // If retrieve returned `None` + .attach_printable("Merchant not authenticated")?; + + if stored_api_key + .expires_at + .map(|expires_at| expires_at < date_time::now()) + .unwrap_or(false) + { + return Err(report!(errors::ApiErrorResponse::Unauthorized)) + .attach_printable("API key has expired"); + } + + let key_manager_state = &(&state.session_state()).into(); + + let key_store = state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")?; + + let merchant = state + .store() + .find_merchant_account_by_merchant_id( + key_manager_state, + &stored_api_key.merchant_id, + &key_store, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + // Get connected merchant account if API call is done by Platform merchant account on behalf of connected merchant account + let (merchant, platform_merchant_account) = if state.conf().platform.enabled { + get_platform_merchant_account(state, request_headers, merchant).await? + } else { + (merchant, None) + }; + + let key_store = if platform_merchant_account.is_some() { + state + .store() + .get_merchant_key_store_by_merchant_id( + key_manager_state, + merchant.get_id(), + &state.store().get_master_key().to_vec().into(), + ) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Failed to fetch merchant key store for the merchant id")? + } else { + key_store + }; + + let profile = state + .store() + .find_business_profile_by_profile_id(key_manager_state, &key_store, &profile_id) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + + let auth = AuthenticationData { + merchant_account: merchant, + platform_merchant_account, + key_store, + profile, + }; + Ok(( + auth.clone(), + AuthenticationType::ApiKey { + merchant_id: auth.merchant_account.get_id().clone(), + key_id: stored_api_key.key_id, + }, + )) + } +} + +#[cfg(feature = "v2")] +#[derive(Debug)] +pub struct V2ClientAuth(pub common_utils::types::authentication::ResourceId); + +#[cfg(feature = "v2")] +#[async_trait] +impl AuthenticateAndFetch for V2ClientAuth +where + A: SessionStateInfo + Sync, +{ + async fn authenticate_and_fetch( + &self, + request_headers: &HeaderMap, + state: &A, + ) -> RouterResult<(AuthenticationData, AuthenticationType)> { + let header_map_struct = HeaderMapStruct::new(request_headers); + let auth_string = header_map_struct.get_auth_string_from_header()?; + + let publishable_key = auth_string + .split(',') + .find_map(|part| part.trim().strip_prefix("publishable-key=")) + .ok_or_else(|| { + report!(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Unable to parse publishable_key") + })?; + + let client_secret = auth_string + .split(',') + .find_map(|part| part.trim().strip_prefix("client-secret=")) + .ok_or_else(|| { + report!(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Unable to parse client_secret") + })?; + + let key_manager_state: &common_utils::types::keymanager::KeyManagerState = + &(&state.session_state()).into(); + + let db_client_secret: diesel_models::ClientSecretType = state + .store() + .get_client_secret(client_secret) + .await + .change_context(errors::ApiErrorResponse::Unauthorized) + .attach_printable("Invalid ephemeral_key")?; + + let profile_id = + get_id_type_by_key_from_headers(headers::X_PROFILE_ID.to_string(), request_headers)? + .get_required_value(headers::X_PROFILE_ID)?; + + match db_client_secret.resource_id { + common_utils::types::authentication::ResourceId::Payment(global_payment_id) => { + return Err(errors::ApiErrorResponse::Unauthorized.into()) + } + common_utils::types::authentication::ResourceId::Customer(global_customer_id) => { + if global_customer_id.get_string_repr() != self.0.to_str() { + return Err(errors::ApiErrorResponse::Unauthorized.into()); + } + } + common_utils::types::authentication::ResourceId::PaymentMethodSession( + global_payment_method_session_id, + ) => { + if global_payment_method_session_id.get_string_repr() != self.0.to_str() { + return Err(errors::ApiErrorResponse::Unauthorized.into()); + } + } + }; + + let (merchant_account, key_store) = state + .store() + .find_merchant_account_by_publishable_key(key_manager_state, publishable_key) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + let merchant_id = merchant_account.get_id().clone(); + + if db_client_secret.merchant_id != merchant_id { + return Err(errors::ApiErrorResponse::Unauthorized.into()); + } + let profile = state + .store() + .find_business_profile_by_merchant_id_profile_id( + key_manager_state, + &key_store, + &merchant_id, + &profile_id, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::Unauthorized)?; + Ok(( + AuthenticationData { + merchant_account, + key_store, + profile, + platform_merchant_account: None, + }, + AuthenticationType::PublishableKey { merchant_id }, + )) + } +} + +#[cfg(feature = "v2")] +pub fn api_or_client_auth<'a, T, A>( + api_auth: &'a dyn AuthenticateAndFetch, + client_auth: &'a dyn AuthenticateAndFetch, + headers: &HeaderMap, +) -> &'a dyn AuthenticateAndFetch +where +{ + if let Ok(val) = HeaderMapStruct::new(headers).get_auth_string_from_header() { + if val.trim().starts_with("api-key=") { + api_auth + } else { + client_auth + } + } else { + api_auth + } +} + #[derive(Debug)] pub struct PublishableKeyAuth; @@ -3282,20 +3500,7 @@ where } } -pub fn is_ephemeral_or_publishible_auth( - headers: &HeaderMap, -) -> RouterResult>> { - let api_key = get_api_key(headers)?; - - if api_key.starts_with("epk") { - Ok(Box::new(EphemeralKeyAuth)) - } else if api_key.starts_with("pk_") { - Ok(Box::new(HeaderAuth(PublishableKeyAuth))) - } else { - Ok(Box::new(HeaderAuth(ApiKeyAuth))) - } -} - +#[cfg(feature = "v1")] pub fn is_ephemeral_auth( headers: &HeaderMap, ) -> RouterResult>> { @@ -3309,10 +3514,13 @@ pub fn is_ephemeral_auth( } pub fn is_jwt_auth(headers: &HeaderMap) -> bool { - headers.get(headers::AUTHORIZATION).is_some() - || get_cookie_from_header(headers) + let header_map_struct = HeaderMapStruct::new(headers); + match header_map_struct.get_auth_string_from_header() { + Ok(auth_str) => auth_str.starts_with("Bearer"), + Err(_) => get_cookie_from_header(headers) .and_then(cookies::get_jwt_from_cookies) - .is_ok() + .is_ok(), + } } pub async fn decode_jwt(token: &str, state: &impl SessionStateInfo) -> RouterResult diff --git a/crates/router/src/types/storage/ephemeral_key.rs b/crates/router/src/types/storage/ephemeral_key.rs index c4b8e2ba701..46bf185194d 100644 --- a/crates/router/src/types/storage/ephemeral_key.rs +++ b/crates/router/src/types/storage/ephemeral_key.rs @@ -1,18 +1,33 @@ -pub use diesel_models::ephemeral_key::{EphemeralKey, EphemeralKeyNew}; #[cfg(feature = "v2")] -pub use diesel_models::ephemeral_key::{EphemeralKeyType, EphemeralKeyTypeNew, ResourceType}; +pub use diesel_models::ephemeral_key::{ClientSecretType, ClientSecretTypeNew}; +pub use diesel_models::ephemeral_key::{EphemeralKey, EphemeralKeyNew}; #[cfg(feature = "v2")] -use crate::types::transformers::ForeignFrom; +use crate::db::errors; +#[cfg(feature = "v2")] +use crate::types::transformers::ForeignTryFrom; #[cfg(feature = "v2")] -impl ForeignFrom for api_models::ephemeral_key::EphemeralKeyResponse { - fn foreign_from(from: EphemeralKeyType) -> Self { - Self { - customer_id: from.customer_id, - created_at: from.created_at, - expires: from.expires, - secret: from.secret, - id: from.id, +impl ForeignTryFrom for api_models::ephemeral_key::ClientSecretResponse { + type Error = errors::ApiErrorResponse; + fn foreign_try_from(from: ClientSecretType) -> Result { + match from.resource_id { + common_utils::types::authentication::ResourceId::Payment(global_payment_id) => { + Err(errors::ApiErrorResponse::InternalServerError) + } + common_utils::types::authentication::ResourceId::PaymentMethodSession( + global_payment_id, + ) => Err(errors::ApiErrorResponse::InternalServerError), + common_utils::types::authentication::ResourceId::Customer(global_customer_id) => { + Ok(Self { + resource_id: api_models::ephemeral_key::ResourceId::Customer( + global_customer_id.clone(), + ), + created_at: from.created_at, + expires: from.expires, + secret: from.secret, + id: from.id, + }) + } } } } diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index acd6e59431c..b88effea34f 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -543,6 +543,12 @@ pub enum Flow { RelayRetrieve, /// Incoming Relay Webhook Receive IncomingRelayWebhookReceive, + /// Payment Method Session Create + PaymentMethodSessionCreate, + /// Payment Method Session Retrieve + PaymentMethodSessionRetrieve, + /// Update a saved payment method using the payment methods session + PaymentMethodSessionUpdateSavedPaymentMethod, } /// Trait for providing generic behaviour to flow metric From 90ea0764aeb8524cac88031e1e887966a5c4fa76 Mon Sep 17 00:00:00 2001 From: Sagnik Mitra <83326850+ImSagnik007@users.noreply.github.com> Date: Tue, 11 Feb 2025 17:18:14 +0530 Subject: [PATCH 080/133] feat(connector): [INESPAY] Enable Inespay In Dashboard (#7233) --- crates/connector_configs/toml/development.toml | 11 ++++++++++- crates/connector_configs/toml/production.toml | 11 ++++++++++- crates/connector_configs/toml/sandbox.toml | 11 ++++++++++- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 1aa77e61edb..8a0b2038c38 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -4543,4 +4543,13 @@ type="Text" [[xendit.debit]] payment_method_type = "UnionPay" [xendit.connector_auth.HeaderKey] -api_key="API Key" \ No newline at end of file +api_key="API Key" + +[inespay] +[[inespay.bank_debit]] + payment_method_type = "sepa" +[inespay.connector_auth.BodyKey] +api_key="API Key" +key1="API Token" +[inespay.connector_webhook_details] +merchant_secret="API Key" \ No newline at end of file diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index e5fd8c86f8b..63cf2756fa7 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -3414,4 +3414,13 @@ api_secret="Pin" [[xendit.debit]] payment_method_type = "UnionPay" [xendit.connector_auth.HeaderKey] -api_key="API Key" \ No newline at end of file +api_key="API Key" + +[inespay] +[[inespay.bank_debit]] + payment_method_type = "sepa" +[inespay.connector_auth.BodyKey] +api_key="API Key" +key1="API Token" +[inespay.connector_webhook_details] +merchant_secret="API Key" \ No newline at end of file diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index c82c316d910..4c28f29550b 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -4482,4 +4482,13 @@ type="Text" [[xendit.debit]] payment_method_type = "UnionPay" [xendit.connector_auth.HeaderKey] -api_key="API Key" \ No newline at end of file +api_key="API Key" + +[inespay] +[[inespay.bank_debit]] + payment_method_type = "sepa" +[inespay.connector_auth.BodyKey] +api_key="API Key" +key1="API Token" +[inespay.connector_webhook_details] +merchant_secret="API Key" \ No newline at end of file From 60310b485dd78d601a7e25f9b4bc8da53b425ce3 Mon Sep 17 00:00:00 2001 From: sweta-kumari-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> Date: Tue, 11 Feb 2025 22:40:38 +0530 Subject: [PATCH 081/133] feat(connector): [GETNET] add Connector Template Code (#7105) --- config/config.example.toml | 1 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/common_enums/src/connector_enums.rs | 3 + crates/connector_configs/src/connector.rs | 2 + .../hyperswitch_connectors/src/connectors.rs | 22 +- .../src/connectors/getnet.rs | 568 ++++++++++++++++++ .../src/connectors/getnet/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 35 ++ .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 4 +- crates/router/src/core/admin.rs | 4 + .../connector_integration_v2_impls.rs | 3 + crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 3 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/getnet.rs | 421 +++++++++++++ crates/router/tests/connectors/main.rs | 1 + .../router/tests/connectors/sample_auth.toml | 3 + crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 2 + scripts/add_connector.sh | 2 +- 26 files changed, 1324 insertions(+), 13 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/getnet.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/getnet/transformers.rs create mode 100644 crates/router/tests/connectors/getnet.rs diff --git a/config/config.example.toml b/config/config.example.toml index 16b5ef6c3a1..15012c92405 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -213,6 +213,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 01e74f36542..bc46dda1fcb 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -59,6 +59,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index e52d1a1f91a..07ca70b9af4 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -63,6 +63,7 @@ fiuu.base_url = "https://pay.merchant.razer.com/" fiuu.secondary_base_url="https://api.merchant.razer.com/" fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api.gocardless.com" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 548f4aead63..b044641f881 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -63,6 +63,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" diff --git a/config/development.toml b/config/development.toml index a7e33511f17..0cc3193a292 100644 --- a/config/development.toml +++ b/config/development.toml @@ -170,6 +170,7 @@ cards = [ "fiservemea", "fiuu", "forte", + "getnet", "globalpay", "globepay", "gocardless", @@ -281,6 +282,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url = "https://sandbox.merchant.razer.com/" fiuu.third_base_url = "https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 060093550e9..cf5d83e4da2 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -145,6 +145,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url = "https://sandbox.merchant.razer.com/" fiuu.third_base_url = "https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" @@ -249,6 +250,7 @@ cards = [ "fiservemea", "fiuu", "forte", + "getnet", "globalpay", "globepay", "gocardless", diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 2e1fd195b90..61dbfd3f7fa 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -81,6 +81,7 @@ pub enum RoutableConnectors { Fiservemea, Fiuu, Forte, + // Getnet, Globalpay, Globepay, Gocardless, @@ -217,6 +218,7 @@ pub enum Connector { Fiservemea, Fiuu, Forte, + // Getnet, Globalpay, Globepay, Gocardless, @@ -365,6 +367,7 @@ impl Connector { | Self::Fiservemea | Self::Fiuu | Self::Forte + // | Self::Getnet | Self::Globalpay | Self::Globepay | Self::Gocardless diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 3175bbde8f2..c24beba0a47 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -191,6 +191,7 @@ pub struct ConnectorConfig { pub fiservemea: Option, pub fiuu: Option, pub forte: Option, + // pub getnet: Option, pub globalpay: Option, pub globepay: Option, pub gocardless: Option, @@ -353,6 +354,7 @@ impl ConnectorConfig { Connector::Fiservemea => Ok(connector_data.fiservemea), Connector::Fiuu => Ok(connector_data.fiuu), Connector::Forte => Ok(connector_data.forte), + // Connector::Getnet => Ok(connector_data.getnet), Connector::Globalpay => Ok(connector_data.globalpay), Connector::Globepay => Ok(connector_data.globepay), Connector::Gocardless => Ok(connector_data.gocardless), diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 58e6d9c7e5d..a236309e88a 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -25,6 +25,7 @@ pub mod fiserv; pub mod fiservemea; pub mod fiuu; pub mod forte; +pub mod getnet; pub mod globalpay; pub mod globepay; pub mod gocardless; @@ -73,14 +74,15 @@ pub use self::{ chargebee::Chargebee, coinbase::Coinbase, coingate::Coingate, cryptopay::Cryptopay, ctp_mastercard::CtpMastercard, cybersource::Cybersource, datatrans::Datatrans, deutschebank::Deutschebank, digitalvirgo::Digitalvirgo, dlocal::Dlocal, elavon::Elavon, - fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, globalpay::Globalpay, - globepay::Globepay, gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, inespay::Inespay, - itaubank::Itaubank, jpmorgan::Jpmorgan, klarna::Klarna, mifinity::Mifinity, mollie::Mollie, - multisafepay::Multisafepay, nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, - novalnet::Novalnet, nuvei::Nuvei, paybox::Paybox, payeezy::Payeezy, payu::Payu, - placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, - razorpay::Razorpay, redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, - thunes::Thunes, tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, - volt::Volt, wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, - zen::Zen, zsl::Zsl, + fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, getnet::Getnet, + globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, helcim::Helcim, + iatapay::Iatapay, inespay::Inespay, itaubank::Itaubank, jpmorgan::Jpmorgan, klarna::Klarna, + mifinity::Mifinity, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, + nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, nuvei::Nuvei, paybox::Paybox, + payeezy::Payeezy, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, + prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, redsys::Redsys, shift4::Shift4, + square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, tsys::Tsys, + unified_authentication_service::UnifiedAuthenticationService, volt::Volt, + wellsfargo::Wellsfargo, worldline::Worldline, worldpay::Worldpay, xendit::Xendit, zen::Zen, + zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/getnet.rs b/crates/hyperswitch_connectors/src/connectors/getnet.rs new file mode 100644 index 00000000000..557dfbeaca3 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/getnet.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as getnet; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Getnet { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Getnet { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Getnet {} +impl api::PaymentSession for Getnet {} +impl api::ConnectorAccessToken for Getnet {} +impl api::MandateSetup for Getnet {} +impl api::PaymentAuthorize for Getnet {} +impl api::PaymentSync for Getnet {} +impl api::PaymentCapture for Getnet {} +impl api::PaymentVoid for Getnet {} +impl api::Refund for Getnet {} +impl api::RefundExecute for Getnet {} +impl api::RefundSync for Getnet {} +impl api::PaymentToken for Getnet {} + +impl ConnectorIntegration + for Getnet +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Getnet +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Getnet { + fn id(&self) -> &'static str { + "getnet" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + // TODO! Check connector documentation, on which unit they are processing the currency. + // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, + // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.getnet.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = getnet::GetnetAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: getnet::GetnetErrorResponse = res + .response + .parse_struct("GetnetErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Getnet { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Getnet { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Getnet {} + +impl ConnectorIntegration for Getnet {} + +impl ConnectorIntegration for Getnet { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = getnet::GetnetRouterData::from((amount, req)); + let connector_req = getnet::GetnetPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: getnet::GetnetPaymentsResponse = res + .response + .parse_struct("Getnet PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Getnet { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: getnet::GetnetPaymentsResponse = res + .response + .parse_struct("getnet PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Getnet { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: getnet::GetnetPaymentsResponse = res + .response + .parse_struct("Getnet PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Getnet {} + +impl ConnectorIntegration for Getnet { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = getnet::GetnetRouterData::from((refund_amount, req)); + let connector_req = getnet::GetnetRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: getnet::RefundResponse = + res.response + .parse_struct("getnet RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Getnet { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: getnet::RefundResponse = res + .response + .parse_struct("getnet RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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] +impl webhooks::IncomingWebhook for Getnet { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Getnet {} diff --git a/crates/hyperswitch_connectors/src/connectors/getnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/getnet/transformers.rs new file mode 100644 index 00000000000..a6dd52d83f5 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/getnet/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct GetnetRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for GetnetRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct GetnetPaymentsRequest { + amount: StringMinorUnit, + card: GetnetCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct GetnetCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&GetnetRouterData<&PaymentsAuthorizeRouterData>> for GetnetPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &GetnetRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = GetnetCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct GetnetAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for GetnetAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum GetnetPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: GetnetPaymentStatus) -> Self { + match item { + GetnetPaymentStatus::Succeeded => Self::Charged, + GetnetPaymentStatus::Failed => Self::Failure, + GetnetPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct GetnetPaymentsResponse { + status: GetnetPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct GetnetRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&GetnetRouterData<&RefundsRouterData>> for GetnetRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &GetnetRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct GetnetErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index f34b09e8f35..f2f7e6b441b 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -121,6 +121,7 @@ default_imp_for_authorize_session_token!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -203,6 +204,7 @@ default_imp_for_calculate_tax!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -284,6 +286,7 @@ default_imp_for_session_update!( connectors::Fiserv, connectors::Fiservemea, connectors::Forte, + connectors::Getnet, connectors::Helcim, connectors::Iatapay, connectors::Inespay, @@ -369,6 +372,7 @@ default_imp_for_post_session_tokens!( connectors::Fiserv, connectors::Fiservemea, connectors::Forte, + connectors::Getnet, connectors::Helcim, connectors::Iatapay, connectors::Inespay, @@ -449,6 +453,7 @@ default_imp_for_complete_authorize!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globepay, connectors::Gocardless, connectors::Helcim, @@ -524,6 +529,7 @@ default_imp_for_incremental_authorization!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -608,6 +614,7 @@ default_imp_for_create_customer!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Helcim, @@ -688,6 +695,7 @@ default_imp_for_connector_redirect_response!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globepay, connectors::Gocardless, connectors::Helcim, @@ -763,6 +771,7 @@ default_imp_for_pre_processing_steps!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Helcim, @@ -844,6 +853,7 @@ default_imp_for_post_processing_steps!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -929,6 +939,7 @@ default_imp_for_approve!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1014,6 +1025,7 @@ default_imp_for_reject!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1099,6 +1111,7 @@ default_imp_for_webhook_source_verification!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1185,6 +1198,7 @@ default_imp_for_accept_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1270,6 +1284,7 @@ default_imp_for_submit_evidence!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1355,6 +1370,7 @@ default_imp_for_defend_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1449,6 +1465,7 @@ default_imp_for_file_upload!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1526,6 +1543,7 @@ default_imp_for_payouts!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1612,6 +1630,7 @@ default_imp_for_payouts_create!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1699,6 +1718,7 @@ default_imp_for_payouts_retrieve!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1786,6 +1806,7 @@ default_imp_for_payouts_eligibility!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1872,6 +1893,7 @@ default_imp_for_payouts_fulfill!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1959,6 +1981,7 @@ default_imp_for_payouts_cancel!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2046,6 +2069,7 @@ default_imp_for_payouts_quote!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2133,6 +2157,7 @@ default_imp_for_payouts_recipient!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2220,6 +2245,7 @@ default_imp_for_payouts_recipient_account!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2307,6 +2333,7 @@ default_imp_for_frm_sale!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2394,6 +2421,7 @@ default_imp_for_frm_checkout!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2481,6 +2509,7 @@ default_imp_for_frm_transaction!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2568,6 +2597,7 @@ default_imp_for_frm_fulfillment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2655,6 +2685,7 @@ default_imp_for_frm_record_return!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2738,6 +2769,7 @@ default_imp_for_revoking_mandates!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2823,6 +2855,7 @@ default_imp_for_uas_pre_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2906,6 +2939,7 @@ default_imp_for_uas_post_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2989,6 +3023,7 @@ default_imp_for_uas_authentication!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index d0241967acb..93623f3d7f0 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -231,6 +231,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -317,6 +318,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -398,6 +400,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -484,6 +487,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -569,6 +573,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -655,6 +660,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -751,6 +757,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -839,6 +846,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -927,6 +935,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1015,6 +1024,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1103,6 +1113,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1191,6 +1202,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1279,6 +1291,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1367,6 +1380,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1455,6 +1469,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1541,6 +1556,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1629,6 +1645,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1717,6 +1734,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1805,6 +1823,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1893,6 +1912,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -1981,6 +2001,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, @@ -2066,6 +2087,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Fiservemea, connectors::Fiuu, connectors::Forte, + connectors::Getnet, connectors::Globalpay, connectors::Globepay, connectors::Gocardless, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index ecb3aea102a..8781060eb9e 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -43,6 +43,7 @@ pub struct Connectors { pub fiservemea: ConnectorParams, pub fiuu: ConnectorParamsWithThreeUrls, pub forte: ConnectorParams, + pub getnet: ConnectorParams, pub globalpay: ConnectorParams, pub globepay: ConnectorParams, pub gocardless: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index e6767fbb08b..c8ac040ff8b 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -35,8 +35,8 @@ pub use hyperswitch_connectors::connectors::{ datatrans::Datatrans, deutschebank, deutschebank::Deutschebank, digitalvirgo, digitalvirgo::Digitalvirgo, dlocal, dlocal::Dlocal, elavon, elavon::Elavon, fiserv, fiserv::Fiserv, fiservemea, fiservemea::Fiservemea, fiuu, fiuu::Fiuu, forte, forte::Forte, - globalpay, globalpay::Globalpay, globepay, globepay::Globepay, gocardless, - gocardless::Gocardless, helcim, helcim::Helcim, iatapay, iatapay::Iatapay, inespay, + getnet, getnet::Getnet, globalpay, globalpay::Globalpay, globepay, globepay::Globepay, + gocardless, gocardless::Gocardless, helcim, helcim::Helcim, iatapay, iatapay::Iatapay, inespay, inespay::Inespay, itaubank, itaubank::Itaubank, jpmorgan, jpmorgan::Jpmorgan, klarna, klarna::Klarna, mifinity, mifinity::Mifinity, mollie, mollie::Mollie, multisafepay, multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index a75d16a7300..b3f1eab9d9b 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1370,6 +1370,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { forte::transformers::ForteAuthType::try_from(self.auth_type)?; Ok(()) } + // api_enums::Connector::Getnet => { + // getnet::transformers::GetnetAuthType::try_from(self.auth_type)?; + // Ok(()) + // } api_enums::Connector::Globalpay => { globalpay::transformers::GlobalpayAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 7a43ec1a189..b9595c1b23d 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1002,6 +1002,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Fiservemea, connector::Fiuu, connector::Forte, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gpayments, @@ -1466,6 +1467,7 @@ default_imp_for_new_connector_integration_frm!( connector::Fiservemea, connector::Forte, connector::Fiuu, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gpayments, @@ -1839,6 +1841,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Fiservemea, connector::Forte, connector::Fiuu, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gocardless, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 6728d51558c..e311c37ffab 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -422,6 +422,7 @@ default_imp_for_connector_request_id!( connector::Fiservemea, connector::Fiuu, connector::Forte, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gocardless, @@ -1379,6 +1380,7 @@ default_imp_for_fraud_check!( connector::Fiservemea, connector::Fiuu, connector::Forte, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gocardless, @@ -1914,6 +1916,7 @@ default_imp_for_connector_authentication!( connector::Fiservemea, connector::Fiuu, connector::Forte, + connector::Getnet, connector::Globalpay, connector::Globepay, connector::Gocardless, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index addb34eb973..ab3572d2661 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -430,6 +430,9 @@ impl ConnectorData { enums::Connector::Forte => { Ok(ConnectorEnum::Old(Box::new(connector::Forte::new()))) } + // enums::Connector::Getnet => { + // Ok(ConnectorEnum::Old(Box::new(connector::Getnet::new()))) + // } enums::Connector::Globalpay => { Ok(ConnectorEnum::Old(Box::new(connector::Globalpay::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index a945f8ebfb4..40d07490b83 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -246,6 +246,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Fiservemea => Self::Fiservemea, api_enums::Connector::Fiuu => Self::Fiuu, api_enums::Connector::Forte => Self::Forte, + // api_enums::Connector::Getnet => Self::Getnet, api_enums::Connector::Globalpay => Self::Globalpay, api_enums::Connector::Globepay => Self::Globepay, api_enums::Connector::Gocardless => Self::Gocardless, diff --git a/crates/router/tests/connectors/getnet.rs b/crates/router/tests/connectors/getnet.rs new file mode 100644 index 00000000000..d8441df440b --- /dev/null +++ b/crates/router/tests/connectors/getnet.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct GetnetTest; +impl ConnectorActions for GetnetTest {} +impl utils::Connector for GetnetTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Getnet; + utils::construct_connector_data_old( + Box::new(Getnet::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .getnet + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "getnet".to_string() + } +} + +static CONNECTOR: GetnetTest = GetnetTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 19f0b9f9b2c..7e9e49c06a0 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -38,6 +38,7 @@ mod fiserv; mod fiservemea; mod fiuu; mod forte; +mod getnet; mod globalpay; mod globepay; mod gocardless; diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index 44c178f3a3b..bf8b35d2fe0 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -290,6 +290,9 @@ api_secret = "Client Key" [thunes] api_key="API Key" +[getnet] +api_key="API Key" + [inespay] api_key="API Key" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 0a3aa4bcff0..8e733330517 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -45,6 +45,7 @@ pub struct ConnectorAuthentication { pub fiservemea: Option, pub fiuu: Option, pub forte: Option, + pub getnet: Option, pub globalpay: Option, pub globepay: Option, pub gocardless: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index f0f44fc207d..c2cc8833f87 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -111,6 +111,7 @@ fiuu.base_url = "https://sandbox.merchant.razer.com/" fiuu.secondary_base_url="https://sandbox.merchant.razer.com/" fiuu.third_base_url="https://api.merchant.razer.com/" forte.base_url = "https://sandbox.forte.net/api/v3" +getnet.base_url = "https://api-test.getneteurope.com/engine/rest" globalpay.base_url = "https://apis.sandbox.globalpay.com/ucp/" globepay.base_url = "https://pay.globepay.co/" gocardless.base_url = "https://api-sandbox.gocardless.com" @@ -215,6 +216,7 @@ cards = [ "fiservemea", "fiuu", "forte", + "getnet", "globalpay", "globepay", "gocardless", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 8dee6cd21f9..049b4db3f8f 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase coingate cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From b38a958a9218df2ae4b10c38ca81f4e862ebde3d Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 12 Feb 2025 00:27:08 +0000 Subject: [PATCH 082/133] chore(version): 2025.02.12.0 --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ed74329eef4..52b0d36a756 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,27 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.12.0 + +### Features + +- **connector:** + - [INESPAY] Enable Inespay In Dashboard ([#7233](https://github.com/juspay/hyperswitch/pull/7233)) ([`90ea076`](https://github.com/juspay/hyperswitch/commit/90ea0764aeb8524cac88031e1e887966a5c4fa76)) + - [GETNET] add Connector Template Code ([#7105](https://github.com/juspay/hyperswitch/pull/7105)) ([`60310b4`](https://github.com/juspay/hyperswitch/commit/60310b485dd78d601a7e25f9b4bc8da53b425ce3)) +- **payment_methods_session_v2:** Add payment methods session endpoints ([#7107](https://github.com/juspay/hyperswitch/pull/7107)) ([`9615382`](https://github.com/juspay/hyperswitch/commit/96153824a73f359623bf77f199013d2ca9ff5e43)) + +### Bug Fixes + +- **payments:** [Payment links] Add fix for payment link redirection url ([#7232](https://github.com/juspay/hyperswitch/pull/7232)) ([`1d607d7`](https://github.com/juspay/hyperswitch/commit/1d607d7970abe204bc6101a81ba26652eadcbd04)) + +### Refactors + +- **core:** Add support for expand attempt list in psync v2 ([#7209](https://github.com/juspay/hyperswitch/pull/7209)) ([`d093317`](https://github.com/juspay/hyperswitch/commit/d09331701997b70672d4d768e8139c12fffb7ad1)) + +**Full Changelog:** [`2025.02.11.0...2025.02.12.0`](https://github.com/juspay/hyperswitch/compare/2025.02.11.0...2025.02.12.0) + +- - - + ## 2025.02.11.0 ### Features From 055f62858e6d0bcc6d27f563b30804365106d4a6 Mon Sep 17 00:00:00 2001 From: Pa1NarK <69745008+pixincreate@users.noreply.github.com> Date: Wed, 12 Feb 2025 12:58:05 +0530 Subject: [PATCH 083/133] refactor(cypress): make amount configurable (#7219) --- .../cypress/e2e/configs/Payment/Adyen.js | 52 +++--- .../e2e/configs/Payment/BankOfAmerica.js | 65 ++------ .../cypress/e2e/configs/Payment/Bluesnap.js | 65 ++------ .../cypress/e2e/configs/Payment/Checkout.js | 53 ++---- .../cypress/e2e/configs/Payment/Commons.js | 93 ++++------- .../e2e/configs/Payment/Cybersource.js | 71 +++----- .../cypress/e2e/configs/Payment/Datatrans.js | 41 ++--- .../e2e/configs/Payment/Deutschebank.js | 42 ++--- .../cypress/e2e/configs/Payment/Elavon.js | 55 ++----- .../cypress/e2e/configs/Payment/Fiservemea.js | 41 ++--- .../cypress/e2e/configs/Payment/Fiuu.js | 43 ++--- .../cypress/e2e/configs/Payment/Iatapay.js | 2 +- .../cypress/e2e/configs/Payment/Jpmorgan.js | 42 ++--- .../cypress/e2e/configs/Payment/Nexixpay.js | 58 +++---- .../cypress/e2e/configs/Payment/Nmi.js | 53 ++---- .../cypress/e2e/configs/Payment/Noon.js | 55 ++----- .../cypress/e2e/configs/Payment/Novalnet.js | 39 ++--- .../cypress/e2e/configs/Payment/Paybox.js | 45 ++---- .../cypress/e2e/configs/Payment/Paypal.js | 59 ++----- .../cypress/e2e/configs/Payment/Stripe.js | 64 ++------ .../cypress/e2e/configs/Payment/Trustpay.js | 48 +----- .../cypress/e2e/configs/Payment/WellsFargo.js | 41 ++--- .../cypress/e2e/configs/Payment/WorldPay.js | 44 ++--- .../cypress/e2e/configs/Payment/Xendit.js | 72 +++------ .../e2e/configs/PaymentMethodList/Commons.js | 4 +- .../cypress/e2e/configs/Routing/Adyen.js | 8 +- .../cypress/e2e/configs/Routing/Stripe.js | 6 +- .../00006-NoThreeDSManualCapture.cy.js | 8 +- .../spec/Payment/00009-RefundPayment.cy.js | 79 ++++----- .../e2e/spec/Payment/00010-SyncRefund.cy.js | 2 +- .../00011-CreateSingleuseMandate.cy.js | 18 +-- .../Payment/00012-CreateMultiuseMandate.cy.js | 24 +-- .../Payment/00013-ListAndRevokeMandate.cy.js | 6 +- .../e2e/spec/Payment/00014-SaveCardFlow.cy.js | 8 +- .../spec/Payment/00015-ZeroAuthMandate.cy.js | 6 +- .../Payment/00016-ThreeDSManualCapture.cy.js | 8 +- .../Payment/00019-MandatesUsingPMID.cy.js | 40 ++--- .../00020-MandatesUsingNTIDProxy.cy.js | 22 +-- .../cypress/e2e/spec/Payment/00021-UPI.cy.js | 2 +- .../e2e/spec/Payment/00022-Variations.cy.js | 121 ++++++-------- .../00026-DeletedCustomerPsyncFlow.cy.js | 4 +- .../spec/Payment/00029-IncrementalAuth.cy.js | 4 +- .../cypress/fixtures/create-confirm-body.json | 2 +- .../cypress/fixtures/create-mandate-cit.json | 2 +- .../cypress/fixtures/create-payment-body.json | 2 +- cypress-tests/cypress/support/commands.js | 153 ++++++++++-------- 46 files changed, 596 insertions(+), 1176 deletions(-) diff --git a/cypress-tests/cypress/e2e/configs/Payment/Adyen.js b/cypress-tests/cypress/e2e/configs/Payment/Adyen.js index d5bdd190e95..b317366dbf2 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Adyen.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Adyen.js @@ -19,7 +19,7 @@ const successfulThreeDSTestCardDetails = { const failedNo3DSCardDetails = { card_number: "4242424242424242", card_exp_month: "01", - card_exp_year: "25", + card_exp_year: "35", card_holder_name: "joseph Doe", card_cvc: "123", }; @@ -75,7 +75,7 @@ export const connectorDetails = { }, PaymentIntentOffSession: { Request: { - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", currency: "USD", customer_acceptance: null, @@ -98,7 +98,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -116,9 +116,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -212,32 +212,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: 0, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: 0, }, }, @@ -259,7 +256,7 @@ export const connectorDetails = { }), Refund: { Request: { - currency: "USD", + amount: 6000, }, Response: { status: 200, @@ -270,7 +267,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - currency: "USD", + amount: 2000, }, Response: { status: 200, @@ -281,12 +278,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 400, @@ -302,12 +294,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 400, @@ -322,9 +309,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - currency: "USD", - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js b/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js index 0c8a8803254..ef953956c5d 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js +++ b/cypress-tests/cypress/e2e/configs/Payment/BankOfAmerica.js @@ -68,7 +68,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "USD", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", @@ -89,7 +89,7 @@ export const connectorDetails = { status: 200, body: { status: "requires_payment_method", - amount: 6500, + amount: 6000, shipping_cost: 50, }, }, @@ -108,9 +108,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -172,32 +172,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -212,12 +209,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -229,12 +221,7 @@ export const connectorDetails = { PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -245,12 +232,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -261,12 +243,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -276,14 +253,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Bluesnap.js b/cypress-tests/cypress/e2e/configs/Payment/Bluesnap.js index f27b7ef76c1..b80701468ac 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Bluesnap.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Bluesnap.js @@ -32,14 +32,14 @@ export const connectorDetails = { PaymentIntentWithShippingCost: { Request: { currency: "USD", - amount: 6500, + amount: 6000, shipping_cost: 50, }, Response: { status: 200, body: { status: "requires_payment_method", - amount: 6500, + amount: 6000, shipping_cost: 50, }, }, @@ -58,9 +58,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -140,32 +140,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -180,12 +177,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -196,12 +188,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -212,12 +199,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -228,12 +210,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -243,14 +220,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Checkout.js b/cypress-tests/cypress/e2e/configs/Payment/Checkout.js index 9679b70866b..742b9daf50d 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Checkout.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Checkout.js @@ -48,7 +48,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -66,7 +66,7 @@ export const connectorDetails = { body: { status: "processing", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -136,31 +136,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -175,11 +173,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -190,11 +184,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -205,11 +195,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -220,11 +206,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -234,13 +216,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Commons.js b/cypress-tests/cypress/e2e/configs/Payment/Commons.js index f021a0a98fb..0f551c97c63 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Commons.js @@ -567,7 +567,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }), @@ -649,16 +649,13 @@ export const connectorDetails = { }), Capture: getCustomExchange({ Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, }), PartialCapture: getCustomExchange({ - Request: {}, + Request: { + amount_to_capture: 2000, + }, }), Void: getCustomExchange({ Request: {}, @@ -704,32 +701,12 @@ export const connectorDetails = { }), Refund: getCustomExchange({ Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, - ResponseCustom: { - status: 400, - body: { - error: { - type: "invalid_request", - message: "The refund amount exceeds the amount captured", - code: "IR_13", - }, - }, + amount: 6000, }, }), manualPaymentRefund: getCustomExchange({ Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -740,12 +717,7 @@ export const connectorDetails = { }), manualPaymentPartialRefund: getCustomExchange({ Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -756,24 +728,10 @@ export const connectorDetails = { }), PartialRefund: getCustomExchange({ Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, - }), - SyncRefund: getCustomExchange({ - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, }), + SyncRefund: getCustomExchange({}), MandateSingleUse3DSAutoCapture: getCustomExchange({ Request: { payment_method: "card", @@ -1336,14 +1294,7 @@ export const connectorDetails = { }, CaptureGreaterAmount: { Request: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, + amount_to_capture: 6000000, }, Response: { status: 400, @@ -1359,12 +1310,7 @@ export const connectorDetails = { CaptureCapturedAmount: getCustomExchange({ Request: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, }, Response: { @@ -1400,6 +1346,21 @@ export const connectorDetails = { }, }, }), + RefundGreaterAmount: { + Request: { + amount: 6000000, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: "The refund amount exceeds the amount captured", + code: "IR_13", + }, + }, + }, + }, MITAutoCapture: getCustomExchange({ Request: {}, Response: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Cybersource.js b/cypress-tests/cypress/e2e/configs/Payment/Cybersource.js index 804f9aca0dc..ae06e28332c 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Cybersource.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Cybersource.js @@ -136,7 +136,7 @@ export const connectorDetails = { }, Request: { currency: "USD", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", @@ -176,7 +176,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -194,9 +194,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -306,19 +306,14 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: null, }, }, @@ -329,13 +324,15 @@ export const connectorDetails = { value: "connector_1", }, }, - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: null, }, }, @@ -361,12 +358,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -382,12 +374,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 400, @@ -408,12 +395,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 400, @@ -434,12 +416,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -454,14 +431,6 @@ export const connectorDetails = { value: "connector_1", }, }, - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -471,14 +440,14 @@ export const connectorDetails = { }, IncrementalAuth: { Request: { - amount: 7000, + amount: 6000, }, Response: { status: 200, body: { status: "requires_capture", - amount: 7000, - amount_capturable: 7000, + amount: 6000, + amount_capturable: 6000, amount_received: null, }, }, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Datatrans.js b/cypress-tests/cypress/e2e/configs/Payment/Datatrans.js index 13bbf7e7d34..57e28b86224 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Datatrans.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Datatrans.js @@ -57,32 +57,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -97,12 +94,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -113,12 +105,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -128,14 +115,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Deutschebank.js b/cypress-tests/cypress/e2e/configs/Payment/Deutschebank.js index 591e6c8869e..7e669ffb502 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Deutschebank.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Deutschebank.js @@ -99,31 +99,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -132,11 +130,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -150,11 +144,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -168,11 +158,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -186,11 +172,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successful3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Elavon.js b/cypress-tests/cypress/e2e/configs/Payment/Elavon.js index 6755dd16e70..d15d2174563 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Elavon.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Elavon.js @@ -120,12 +120,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -136,12 +131,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -424,43 +414,35 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -484,12 +466,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -499,14 +476,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Fiservemea.js b/cypress-tests/cypress/e2e/configs/Payment/Fiservemea.js index 1460b474220..9ee05d419c1 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Fiservemea.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Fiservemea.js @@ -57,32 +57,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "EUR", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -97,12 +94,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "EUR", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -113,12 +105,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "EUR", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -128,14 +115,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "EUR", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Fiuu.js b/cypress-tests/cypress/e2e/configs/Payment/Fiuu.js index a707643248e..74ffd403699 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Fiuu.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Fiuu.js @@ -132,32 +132,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -172,12 +169,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -188,12 +180,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -203,14 +190,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -384,7 +363,7 @@ export const connectorDetails = { }, PaymentIntentOffSession: { Request: { - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", currency: "USD", customer_acceptance: null, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Iatapay.js b/cypress-tests/cypress/e2e/configs/Payment/Iatapay.js index 47772896955..bbd099edf84 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Iatapay.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Iatapay.js @@ -160,7 +160,7 @@ export const connectorDetails = { }, Refund: { Request: { - amount: 6500, + amount: 6000, }, Response: { status: 200, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Jpmorgan.js b/cypress-tests/cypress/e2e/configs/Payment/Jpmorgan.js index 36e53ffd995..9290367b8b8 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Jpmorgan.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Jpmorgan.js @@ -100,31 +100,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -133,11 +131,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 501, @@ -153,11 +147,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 501, @@ -173,11 +163,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 501, @@ -193,11 +179,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 501, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Nexixpay.js b/cypress-tests/cypress/e2e/configs/Payment/Nexixpay.js index 3b77a0ad6e7..b4b0bdb2136 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Nexixpay.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Nexixpay.js @@ -75,7 +75,7 @@ export const connectorDetails = { PaymentIntent: { Request: { currency: "EUR", - amount: 6500, + amount: 6000, customer_acceptance: null, setup_future_usage: "on_session", billing: billingAddress, @@ -90,7 +90,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "EUR", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", @@ -180,18 +180,14 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: null, }, }, @@ -200,14 +196,16 @@ export const connectorDetails = { Configs: { TRIGGER_SKIP: true, }, - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, - amount_received: 100, + amount: 6000, + amount_capturable: 6000, + amount_received: 2000, }, }, }, @@ -225,11 +223,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -243,11 +237,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -257,13 +247,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -410,12 +393,7 @@ export const connectorDetails = { TRIGGER_SKIP: true, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - currency: "EUR", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -453,7 +431,7 @@ export const connectorDetails = { card: successfulThreeDSTestCardDetails, }, currency: "EUR", - amount: 6500, + amount: 6000, mandate_data: null, customer_acceptance: customerAcceptance, }, @@ -474,7 +452,7 @@ export const connectorDetails = { card: successfulThreeDSTestCardDetails, }, currency: "EUR", - amount: 6500, + amount: 6000, mandate_data: null, customer_acceptance: customerAcceptance, }, @@ -495,7 +473,7 @@ export const connectorDetails = { card: successfulThreeDSTestCardDetails, }, currency: "EUR", - amount: 6500, + amount: 6000, mandate_data: null, authentication_type: "three_ds", customer_acceptance: customerAcceptance, @@ -517,7 +495,7 @@ export const connectorDetails = { card: successfulThreeDSTestCardDetails, }, currency: "EUR", - amount: 6500, + amount: 6000, mandate_data: null, authentication_type: "three_ds", customer_acceptance: customerAcceptance, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js index e8cdd97e7a7..d39b45172af 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js @@ -48,7 +48,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -66,7 +66,7 @@ export const connectorDetails = { body: { status: "processing", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -136,29 +136,27 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, }, }, }, @@ -187,11 +185,7 @@ export const connectorDetails = { Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -202,11 +196,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -217,11 +207,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -232,11 +218,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -246,13 +228,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Noon.js b/cypress-tests/cypress/e2e/configs/Payment/Noon.js index 63f6b631732..61733a1aa3d 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Noon.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Noon.js @@ -101,7 +101,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "AED", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", @@ -125,7 +125,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -143,7 +143,7 @@ export const connectorDetails = { body: { status: "requires_customer_action", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -227,29 +227,27 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, }, }, @@ -265,12 +263,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "AED", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -281,12 +274,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "AED", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -298,11 +286,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -314,11 +298,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -328,13 +308,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Novalnet.js b/cypress-tests/cypress/e2e/configs/Payment/Novalnet.js index 1025b474114..bafb45d0343 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Novalnet.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Novalnet.js @@ -151,31 +151,29 @@ export const connectorDetails = { // }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -190,11 +188,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -205,11 +199,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -219,13 +209,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulThreeDSTestCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -248,7 +231,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "EUR", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", diff --git a/cypress-tests/cypress/e2e/configs/Payment/Paybox.js b/cypress-tests/cypress/e2e/configs/Payment/Paybox.js index 2b85b7e5cd9..f70217059e1 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Paybox.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Paybox.js @@ -27,7 +27,7 @@ const singleUseMandateData = { customer_acceptance: customerAcceptance, mandate_type: { single_use: { - amount: 7000, + amount: 6000, currency: "EUR", }, }, @@ -37,7 +37,7 @@ const multiUseMandateData = { customer_acceptance: customerAcceptance, mandate_type: { multi_use: { - amount: 6500, + amount: 6000, currency: "EUR", }, }, @@ -69,7 +69,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "EUR", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", @@ -155,31 +155,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -199,11 +197,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -214,11 +208,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -228,13 +218,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -373,7 +356,7 @@ export const connectorDetails = { MITAutoCapture: { Request: { currency: "EUR", - amount: 6500, + amount: 6000, }, Response: { status: 200, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Paypal.js b/cypress-tests/cypress/e2e/configs/Payment/Paypal.js index 787ad2afabd..982f082631d 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Paypal.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Paypal.js @@ -70,7 +70,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -169,32 +169,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -209,12 +206,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -225,12 +217,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -241,12 +228,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -257,12 +239,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -272,14 +249,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { @@ -348,7 +317,7 @@ export const connectorDetails = { PaymentIntentOffSession: { Request: { currency: "USD", - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", customer_acceptance: null, setup_future_usage: "off_session", diff --git a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js index b73bbf18dcc..9e557065f4c 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Stripe.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Stripe.js @@ -151,7 +151,7 @@ export const connectorDetails = { Request: { currency: "USD", customer_acceptance: null, - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", setup_future_usage: "off_session", }, @@ -190,7 +190,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -208,9 +208,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -316,32 +316,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -356,12 +353,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -372,11 +364,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -387,12 +375,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -403,12 +386,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -418,14 +396,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/Trustpay.js b/cypress-tests/cypress/e2e/configs/Payment/Trustpay.js index cfd3332eaba..8ef1d4a4a5f 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Trustpay.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Trustpay.js @@ -64,7 +64,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -82,9 +82,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, @@ -142,12 +142,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 400, @@ -173,16 +168,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - paymentSuccessfulStatus: "succeeded", - paymentSyncStatus: "succeeded", - refundStatus: "succeeded", - refundSyncStatus: "succeeded", - customer_acceptance: null, + amount_to_capture: 2000, }, Response: { status: 400, @@ -213,12 +199,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -235,12 +216,7 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -257,14 +233,6 @@ export const connectorDetails = { TIMEOUT: 15000, }, }, - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/WellsFargo.js b/cypress-tests/cypress/e2e/configs/Payment/WellsFargo.js index 38a5b637040..9c90568c3cb 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/WellsFargo.js +++ b/cypress-tests/cypress/e2e/configs/Payment/WellsFargo.js @@ -144,32 +144,29 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, PartialCapture: { - Request: {}, + Request: { + amount_to_capture: 2000, + }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000, }, }, }, @@ -184,12 +181,7 @@ export const connectorDetails = { }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 6000, }, Response: { status: 200, @@ -200,12 +192,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, + amount: 2000, }, Response: { status: 200, @@ -215,14 +202,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "USD", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js b/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js index 3e13aeba754..06c5c799156 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js +++ b/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js @@ -161,25 +161,20 @@ export const connectorDetails = { }, Capture: { Request: { - payment_method: "card", - payment_method_type: "debit", - payment_method_data: { - card: successfulNoThreeDsCardDetailsRequest, - }, - currency: "USD", - customer_acceptance: null, + amount_to_capture: 6000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, }, }, }, PartialCapture: { Request: { + amount_to_capture: 2000, payment_method: "card", payment_method_type: "debit", payment_method_data: { @@ -192,7 +187,7 @@ export const connectorDetails = { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, }, }, @@ -397,7 +392,9 @@ export const connectorDetails = { }, }, Refund: { - Request: {}, + Request: { + amount: 6000, + }, Response: { body: { status: "succeeded", @@ -405,7 +402,9 @@ export const connectorDetails = { }, }, PartialRefund: { - Request: {}, + Request: { + amount: 2000, + }, Response: { body: { status: "succeeded", @@ -414,11 +413,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNoThreeDsCardDetailsRequest, - }, - currency: "USD", + amount: 6000, }, Response: { body: { @@ -428,11 +423,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNoThreeDsCardDetailsRequest, - }, - currency: "USD", + amount: 2000, }, Response: { body: { @@ -441,7 +432,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: {}, Response: { body: { status: "succeeded", @@ -514,7 +504,7 @@ export const connectorDetails = { }, PaymentIntentOffSession: { Request: { - amount: 6500, + amount: 6000, authentication_type: "no_three_ds", currency: "USD", customer_acceptance: null, @@ -555,7 +545,7 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 50, - amount: 6500, + amount: 6000, }, }, }, @@ -573,9 +563,9 @@ export const connectorDetails = { body: { status: "succeeded", shipping_cost: 50, - amount_received: 6550, - amount: 6500, - net_amount: 6550, + amount_received: 6050, + amount: 6000, + net_amount: 6050, }, }, }, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Xendit.js b/cypress-tests/cypress/e2e/configs/Payment/Xendit.js index fcc303877b4..985a69154ce 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Xendit.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Xendit.js @@ -71,7 +71,7 @@ export const connectorDetails = { currency: "IDR", customer_acceptance: null, setup_future_usage: "on_session", - amount: 6500000, + amount: 6000000, billing: billingDetails, }, Response: { @@ -91,13 +91,13 @@ export const connectorDetails = { body: { status: "requires_payment_method", shipping_cost: 100, - amount: 6500000, + amount: 6000000, }, }, }, PaymentConfirmWithShippingCost: { Request: { - amount: 6500000, + amount: 6000000, payment_method: "card", payment_method_data: { card: successfulNo3DSCardDetails, @@ -110,7 +110,7 @@ export const connectorDetails = { body: { status: "processing", shipping_cost: 100, - amount: 6500000, + amount: 6000000, }, }, }, @@ -123,7 +123,7 @@ export const connectorDetails = { }, Request: { payment_method: "card", - amount: 6500000, + amount: 6000000, payment_method_data: { card: successfulNo3DSCardDetails, billing: billingDetails, @@ -148,7 +148,7 @@ export const connectorDetails = { }, Request: { payment_method: "card", - amount: 6500000, + amount: 6000000, payment_method_data: { card: successfulNo3DSCardDetails, billing: billingDetails, @@ -166,12 +166,7 @@ export const connectorDetails = { }, manualPaymentPartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, + amount: 2000000, }, Response: { status: 200, @@ -182,12 +177,7 @@ export const connectorDetails = { }, manualPaymentRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, + amount: 6000000, }, Response: { status: 200, @@ -406,7 +396,7 @@ export const connectorDetails = { }, Request: { payment_method: "card", - amount: 6500000, + amount: 6000000, payment_method_data: { card: successfulNo3DSCardDetails, billing: billingDetails, @@ -423,7 +413,7 @@ export const connectorDetails = { }, "3DSManualCapture": { Request: { - amount: 6500000, + amount: 6000000, payment_method: "card", payment_method_data: { card: successfulNo3DSCardDetails, @@ -450,7 +440,7 @@ export const connectorDetails = { }, Request: { payment_method: "card", - amount: 6500000, + amount: 6000000, payment_method_data: { card: successfulNo3DSCardDetails, billing: billingDetails, @@ -473,21 +463,15 @@ export const connectorDetails = { }, }, Request: { - payment_method: "card", - amount: 6500000, - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, + amount_to_capture: 6000000, }, Response: { status: 200, body: { status: "succeeded", - amount: 6500000, + amount: 6000000, amount_capturable: 0, - amount_received: 6500000, + amount_received: 6000000, }, }, }, @@ -499,26 +483,21 @@ export const connectorDetails = { }, }, Request: { - amount: 6500000, + amount_to_capture: 2000000, }, Response: { status: 200, body: { status: "partially_captured", - amount: 6500000, + amount: 2000000, amount_capturable: 0, - amount_received: 100, + amount_received: 2000000, }, }, }, Refund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, + amount: 6000000, }, Response: { status: 200, @@ -542,12 +521,7 @@ export const connectorDetails = { }, PartialRefund: { Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, + amount: 2000000, }, Response: { status: 200, @@ -557,14 +531,6 @@ export const connectorDetails = { }, }, SyncRefund: { - Request: { - payment_method: "card", - payment_method_data: { - card: successfulNo3DSCardDetails, - }, - currency: "IDR", - customer_acceptance: null, - }, Response: { status: 200, body: { diff --git a/cypress-tests/cypress/e2e/configs/PaymentMethodList/Commons.js b/cypress-tests/cypress/e2e/configs/PaymentMethodList/Commons.js index e782b32368b..034598d72d9 100644 --- a/cypress-tests/cypress/e2e/configs/PaymentMethodList/Commons.js +++ b/cypress-tests/cypress/e2e/configs/PaymentMethodList/Commons.js @@ -133,7 +133,7 @@ export const createPaymentBodyWithCurrencyCountry = ( shippingCountry ) => ({ currency: currency, - amount: 6500, + amount: 6000, authentication_type: "three_ds", description: "Joseph First Crypto", email: "hyperswitch_sdk_demo_id@gmail.com", @@ -184,7 +184,7 @@ export const createPaymentBodyWithCurrencyCountry = ( export const createPaymentBodyWithCurrency = (currency) => ({ currency: currency, - amount: 6500, + amount: 6000, authentication_type: "three_ds", description: "Joseph First Crypto", email: "hyperswitch_sdk_demo_id@gmail.com", diff --git a/cypress-tests/cypress/e2e/configs/Routing/Adyen.js b/cypress-tests/cypress/e2e/configs/Routing/Adyen.js index 976de0e2161..3668bf2a3aa 100644 --- a/cypress-tests/cypress/e2e/configs/Routing/Adyen.js +++ b/cypress-tests/cypress/e2e/configs/Routing/Adyen.js @@ -148,8 +148,8 @@ export const connectorDetails = { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: 0, }, }, @@ -160,8 +160,8 @@ export const connectorDetails = { status: 200, body: { status: "processing", - amount: 6500, - amount_capturable: 6500, + amount: 6000, + amount_capturable: 6000, amount_received: 0, }, }, diff --git a/cypress-tests/cypress/e2e/configs/Routing/Stripe.js b/cypress-tests/cypress/e2e/configs/Routing/Stripe.js index e00e3d6ccbb..d55fee41a25 100644 --- a/cypress-tests/cypress/e2e/configs/Routing/Stripe.js +++ b/cypress-tests/cypress/e2e/configs/Routing/Stripe.js @@ -148,9 +148,9 @@ export const connectorDetails = { status: 200, body: { status: "succeeded", - amount: 6500, + amount: 6000, amount_capturable: 0, - amount_received: 6500, + amount_received: 6000, }, }, }, @@ -160,7 +160,7 @@ export const connectorDetails = { status: 200, body: { status: "partially_captured", - amount: 6500, + amount: 6000, amount_capturable: 0, amount_received: 100, }, diff --git a/cypress-tests/cypress/e2e/spec/Payment/00006-NoThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00006-NoThreeDSManualCapture.cy.js index f82284b569f..1a0f28981c8 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00006-NoThreeDSManualCapture.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00006-NoThreeDSManualCapture.cy.js @@ -70,7 +70,7 @@ describe("Card - NoThreeDS Manual payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -124,7 +124,7 @@ describe("Card - NoThreeDS Manual payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -197,7 +197,7 @@ describe("Card - NoThreeDS Manual payment flow test", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -251,7 +251,7 @@ describe("Card - NoThreeDS Manual payment flow test", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00009-RefundPayment.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00009-RefundPayment.cy.js index 1d7ac32f0ba..cf3268e8a5d 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00009-RefundPayment.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00009-RefundPayment.cy.js @@ -67,7 +67,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -135,7 +135,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -145,7 +145,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -202,7 +202,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -262,7 +262,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -273,7 +273,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -344,7 +344,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -362,7 +362,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["manualPaymentRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -430,7 +430,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -448,7 +448,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -457,7 +457,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -528,7 +528,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -544,9 +544,9 @@ describe("Card - Refund flow - No 3DS", () => { it("refund-call-test", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" - ]["manualPaymentRefund"]; + ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -614,7 +614,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -632,7 +632,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -667,7 +667,7 @@ describe("Card - Refund flow - No 3DS", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -686,7 +686,7 @@ describe("Card - Refund flow - No 3DS", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -701,7 +701,7 @@ describe("Card - Refund flow - No 3DS", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -713,7 +713,7 @@ describe("Card - Refund flow - No 3DS", () => { "card_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 7000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -801,7 +801,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -874,7 +874,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -884,7 +884,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 1200, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -943,7 +943,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1005,7 +1005,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -1016,7 +1016,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 3000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -1092,7 +1092,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1110,7 +1110,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["manualPaymentRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1183,7 +1183,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1201,7 +1201,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 5000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1210,7 +1210,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 1500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1283,7 +1283,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1299,9 +1299,9 @@ describe("Card - Refund flow - 3DS", () => { it("refund-call-test", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" - ]["manualPaymentRefund"]; + ]["manualPaymentPartialRefund"]; - cy.refundCallTest(fixtures.refundBody, data, 100, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1374,7 +1374,7 @@ describe("Card - Refund flow - 3DS", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -1390,9 +1390,14 @@ describe("Card - Refund flow - 3DS", () => { it("refund-call-test", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" - ]["manualPaymentRefund"]; + ]["manualPaymentPartialRefund"]; + + const newData = { + ...data, + Request: { amount: data.Request.amount / 2 }, + }; - cy.refundCallTest(fixtures.refundBody, data, 50, globalState); + cy.refundCallTest(fixtures.refundBody, newData, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00010-SyncRefund.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00010-SyncRefund.cy.js index db63105d830..1d1581de5c9 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00010-SyncRefund.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00010-SyncRefund.cy.js @@ -66,7 +66,7 @@ describe("Card - Sync Refund flow test", () => { "Refund" ]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00011-CreateSingleuseMandate.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00011-CreateSingleuseMandate.cy.js index 6fa01f91991..d6b174ba63d 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00011-CreateSingleuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00011-CreateSingleuseMandate.cy.js @@ -34,7 +34,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -53,7 +53,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -81,7 +81,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -97,7 +97,7 @@ describe("Card - SingleUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -111,7 +111,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -123,7 +123,7 @@ describe("Card - SingleUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -154,7 +154,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -170,7 +170,7 @@ describe("Card - SingleUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -184,7 +184,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00012-CreateMultiuseMandate.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00012-CreateMultiuseMandate.cy.js index 2af0b801d2d..b82207117ce 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00012-CreateMultiuseMandate.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00012-CreateMultiuseMandate.cy.js @@ -34,7 +34,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -53,7 +53,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -67,7 +67,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -95,7 +95,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -111,7 +111,7 @@ describe("Card - MultiUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -125,7 +125,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -137,7 +137,7 @@ describe("Card - MultiUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -151,7 +151,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -163,7 +163,7 @@ describe("Card - MultiUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -190,7 +190,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -206,7 +206,7 @@ describe("Card - MultiUse Mandates flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -220,7 +220,7 @@ describe("Card - MultiUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 6500, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00013-ListAndRevokeMandate.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00013-ListAndRevokeMandate.cy.js index f3704b84f97..dba24208b58 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00013-ListAndRevokeMandate.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00013-ListAndRevokeMandate.cy.js @@ -34,7 +34,7 @@ describe("Card - List and revoke Mandates flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -53,7 +53,7 @@ describe("Card - List and revoke Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -112,7 +112,7 @@ describe("Card - List and revoke Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00014-SaveCardFlow.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00014-SaveCardFlow.cy.js index 26c250a3237..29ef0b64a50 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00014-SaveCardFlow.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00014-SaveCardFlow.cy.js @@ -178,7 +178,7 @@ describe("Card - SaveCard payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -271,7 +271,7 @@ describe("Card - SaveCard payment flow test", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -399,7 +399,7 @@ describe("Card - SaveCard payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -450,7 +450,7 @@ describe("Card - SaveCard payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00015-ZeroAuthMandate.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00015-ZeroAuthMandate.cy.js index ba522b3cd71..bb71e1023f5 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00015-ZeroAuthMandate.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00015-ZeroAuthMandate.cy.js @@ -53,7 +53,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -99,7 +99,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -113,7 +113,7 @@ describe("Card - SingleUse Mandates flow test", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 7000, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00016-ThreeDSManualCapture.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00016-ThreeDSManualCapture.cy.js index 89d82f7fb93..41d33f5c258 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00016-ThreeDSManualCapture.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00016-ThreeDSManualCapture.cy.js @@ -75,7 +75,7 @@ describe("Card - ThreeDS Manual payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -135,7 +135,7 @@ describe("Card - ThreeDS Manual payment flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -213,7 +213,7 @@ describe("Card - ThreeDS Manual payment flow test", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -273,7 +273,7 @@ describe("Card - ThreeDS Manual payment flow test", () => { "card_pm" ]["PartialCapture"]; - cy.captureCallTest(fixtures.captureBody, data, 100, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00019-MandatesUsingPMID.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00019-MandatesUsingPMID.cy.js index ec8ddc6c700..ceefef888fc 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00019-MandatesUsingPMID.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00019-MandatesUsingPMID.cy.js @@ -51,7 +51,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -70,7 +70,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -115,7 +115,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -131,7 +131,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -145,7 +145,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -173,7 +173,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -192,7 +192,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -206,7 +206,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -234,7 +234,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -250,7 +250,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -264,7 +264,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -276,7 +276,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -290,7 +290,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -302,7 +302,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -329,7 +329,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 7000, + 6000, true, "automatic", "new_mandate", @@ -353,7 +353,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -367,7 +367,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -395,7 +395,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -416,7 +416,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -430,7 +430,7 @@ describe("Card - Mandates using Payment Method Id flow test", () => { cy.mitUsingPMId( fixtures.pmIdConfirmBody, data, - 7000, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js index 67697cac6c1..6377323bbf6 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00020-MandatesUsingNTIDProxy.cy.js @@ -48,7 +48,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -68,7 +68,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "manual", globalState @@ -88,7 +88,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -102,7 +102,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -124,7 +124,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -136,7 +136,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -150,7 +150,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 6500, + 6000, true, "manual", globalState @@ -162,7 +162,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -181,7 +181,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -195,7 +195,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState @@ -215,7 +215,7 @@ describe("Card - Mandates using Network Transaction Id flow test", () => { cy.mitUsingNTID( fixtures.ntidConfirmBody, data, - 7000, + 6000, true, "automatic", globalState diff --git a/cypress-tests/cypress/e2e/spec/Payment/00021-UPI.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00021-UPI.cy.js index d6b1c99407f..b021f28becd 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00021-UPI.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00021-UPI.cy.js @@ -78,7 +78,7 @@ describe("UPI Payments - Hyperswitch", () => { "upi_pm" ]["Refund"]; - cy.refundCallTest(fixtures.refundBody, data, 6500, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js index 5a1f765cf62..f3a99df1ba2 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js @@ -27,9 +27,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid card number", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCardNumber" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidCardNumber"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -41,9 +41,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid expiry month", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidExpiryMonth" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidExpiryMonth"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -55,9 +55,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid expiry year", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidExpiryYear" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidExpiryYear"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -69,9 +69,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid card CVV", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCardCvv" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidCardCvv"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -83,9 +83,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid currency", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCurrency" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidCurrency"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -97,9 +97,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid capture method", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidCaptureMethod" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidCaptureMethod"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -111,9 +111,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid payment method", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidPaymentMethod" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidPaymentMethod"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -125,9 +125,9 @@ describe("Corner cases", () => { }); it("[Payment] Invalid `amount_to_capture`", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "InvalidAmountToCapture" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["InvalidAmountToCapture"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -139,9 +139,9 @@ describe("Corner cases", () => { }); it("[Payment] Missing required params", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "MissingRequiredParam" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["MissingRequiredParam"]; cy.createConfirmPaymentTest( paymentIntentBody, @@ -165,9 +165,9 @@ describe("Corner cases", () => { }); it("Create payment intent", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "PaymentIntent" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntent"]; cy.createPaymentIntentTest( paymentIntentBody, @@ -179,9 +179,9 @@ describe("Corner cases", () => { }); it("Confirm payment intent", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "PaymentIntentErrored" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PaymentIntentErrored"]; cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); }); @@ -231,11 +231,11 @@ describe("Corner cases", () => { }); it("Capture call", () => { - const data = getConnectorDetails(globalState.get("commons"))["card_pm"][ - "CaptureGreaterAmount" - ]; + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["CaptureGreaterAmount"]; - cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -289,7 +289,7 @@ describe("Corner cases", () => { "card_pm" ]["CaptureCapturedAmount"]; - cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -476,7 +476,7 @@ describe("Corner cases", () => { "CaptureGreaterAmount" ]; - cy.captureCallTest(fixtures.captureBody, data, 65000, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -528,21 +528,9 @@ describe("Corner cases", () => { it("Refund call", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" - ]["Refund"]; - const commonData = getConnectorDetails(globalState.get("commons"))[ - "card_pm" - ]["Refund"]; + ]["RefundGreaterAmount"]; - const newData = { - ...data, - Response: utils.getConnectorFlowDetails( - data, - commonData, - "ResponseCustom" - ), - }; - - cy.refundCallTest(fixtures.refundBody, newData, 65000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); }); @@ -593,21 +581,9 @@ describe("Corner cases", () => { it("Refund call", () => { const data = getConnectorDetails(globalState.get("connectorId"))[ "card_pm" - ]["Refund"]; - const commonData = getConnectorDetails(globalState.get("commons"))[ - "card_pm" - ]["Refund"]; - - const newData = { - ...data, - Response: utils.getConnectorFlowDetails( - data, - commonData, - "ResponseCustom" - ), - }; + ]["RefundGreaterAmount"]; - cy.refundCallTest(fixtures.refundBody, newData, 65000, globalState); + cy.refundCallTest(fixtures.refundBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -640,7 +616,7 @@ describe("Corner cases", () => { cy.citForMandatesCallTest( fixtures.citConfirmBody, data, - 6500, + 6000, true, "manual", "new_mandate", @@ -654,7 +630,7 @@ describe("Corner cases", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -674,13 +650,14 @@ describe("Corner cases", () => { cy.mitForMandatesCallTest( fixtures.mitConfirmBody, data, - 65000, + 60000, true, "manual", globalState ); }); }); + context("Card-NoThreeDS fail payment flow test", () => { let shouldContinue = true; // variable that will be used to skip tests if a previous test fails @@ -724,9 +701,5 @@ describe("Corner cases", () => { cy.confirmCallTest(fixtures.confirmBody, data, true, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); - - it("retrieve-payment-call-test", () => { - cy.retrievePaymentCallTest(globalState); - }); }); }); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js index 365137ee34b..07f285cadfd 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00026-DeletedCustomerPsyncFlow.cy.js @@ -211,7 +211,7 @@ describe("Card - Customer Deletion and Psync", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); @@ -307,7 +307,7 @@ describe("Card - Customer Deletion and Psync", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 6500, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); diff --git a/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js index 76116acc2be..9daa587ecf9 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00029-IncrementalAuth.cy.js @@ -71,7 +71,7 @@ describe.skip("[Payment] Incremental Auth", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 7000, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); @@ -131,7 +131,7 @@ describe.skip("[Payment] Incremental Auth", () => { "card_pm" ]["Capture"]; - cy.captureCallTest(fixtures.captureBody, data, 7000, globalState); + cy.captureCallTest(fixtures.captureBody, data, globalState); if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); diff --git a/cypress-tests/cypress/fixtures/create-confirm-body.json b/cypress-tests/cypress/fixtures/create-confirm-body.json index df533116e56..e0ea67816d1 100644 --- a/cypress-tests/cypress/fixtures/create-confirm-body.json +++ b/cypress-tests/cypress/fixtures/create-confirm-body.json @@ -1,5 +1,5 @@ { - "amount": 6500, + "amount": 6000, "currency": "USD", "confirm": true, "capture_method": "automatic", diff --git a/cypress-tests/cypress/fixtures/create-mandate-cit.json b/cypress-tests/cypress/fixtures/create-mandate-cit.json index d23c1f80543..d011b59d785 100644 --- a/cypress-tests/cypress/fixtures/create-mandate-cit.json +++ b/cypress-tests/cypress/fixtures/create-mandate-cit.json @@ -1,5 +1,5 @@ { - "amount": 7000, + "amount": 6000, "currency": "USD", "confirm": true, "capture_method": "automatic", diff --git a/cypress-tests/cypress/fixtures/create-payment-body.json b/cypress-tests/cypress/fixtures/create-payment-body.json index ea1d22d364e..3b0efb9e8ae 100644 --- a/cypress-tests/cypress/fixtures/create-payment-body.json +++ b/cypress-tests/cypress/fixtures/create-payment-body.json @@ -1,6 +1,6 @@ { "currency": "USD", - "amount": 6500, + "amount": 6000, "authentication_type": "three_ds", "description": "Joseph First Crypto", "email": "hyperswitch_sdk_demo_id@gmail.com", diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index f04d7762fa0..77f94cf30fa 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -119,12 +119,18 @@ Cypress.Commands.add("merchantRetrieveCall", (globalState) => { logRequestId(response.headers["x-request-id"]); cy.wrap(response).then(() => { - expect(response.headers["content-type"]).to.include("application/json"); - expect(response.body.merchant_id).to.equal(merchant_id); - expect(response.body.payment_response_hash_key).to.not.be.empty; - expect(response.body.publishable_key).to.not.be.empty; - expect(response.body.default_profile).to.not.be.empty; - expect(response.body.organization_id).to.not.be.empty; + expect(response.headers["content-type"], "content_headers").to.include( + "application/json" + ); + expect(response.body.merchant_id, "merchant_id").to.equal(merchant_id); + expect( + response.body.payment_response_hash_key, + "payment_response_hash_key" + ).to.not.be.null; + expect(response.body.publishable_key, "publishable_key").to.not.be.null; + cy.log("HI"); + expect(response.body.default_profile, "default_profile").to.not.be.null; + expect(response.body.organization_id, "organization_id").to.not.be.null; globalState.set("organizationId", response.body.organization_id); if (globalState.get("publishableKey") === undefined) { @@ -2161,44 +2167,48 @@ Cypress.Commands.add( } ); -Cypress.Commands.add( - "captureCallTest", - (requestBody, data, amount_to_capture, globalState) => { - const { Configs: configs = {}, Response: resData } = data || {}; +Cypress.Commands.add("captureCallTest", (requestBody, data, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; - const configInfo = execConfig(validateConfig(configs)); - const payment_id = globalState.get("paymentID"); - const profile_id = globalState.get(`${configInfo.profilePrefix}Id`); + const configInfo = execConfig(validateConfig(configs)); + const paymentId = globalState.get("paymentID"); + const profileId = globalState.get(`${configInfo.profilePrefix}Id`); - requestBody.amount_to_capture = amount_to_capture; - requestBody.profile_id = profile_id; + requestBody.profile_id = profileId; - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/payments/${payment_id}/capture`, - headers: { - "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), - }, - failOnStatusCode: false, - body: requestBody, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); + for (const key in reqData) { + requestBody[key] = reqData[key]; + } - cy.wrap(response).then(() => { - expect(response.headers["content-type"]).to.include("application/json"); - if (response.body.capture_method !== undefined) { - expect(response.body.payment_id).to.equal(payment_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } - } else { - defaultErrorHandler(response, resData); + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/payments/${paymentId}/capture`, + headers: { + "Content-Type": "application/json", + "api-key": globalState.get("apiKey"), + }, + failOnStatusCode: false, + body: requestBody, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); + + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.body.capture_method !== undefined) { + expect(response.body.payment_id).to.equal(paymentId); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } - }); + } else { + defaultErrorHandler(response, resData); + } }); - } -); + }); +}); Cypress.Commands.add("voidCallTest", (requestBody, data, globalState) => { const { @@ -2329,46 +2339,47 @@ Cypress.Commands.add( } ); -Cypress.Commands.add( - "refundCallTest", - (requestBody, data, refund_amount, globalState) => { - const { Configs: configs = {}, Response: resData } = data || {}; +Cypress.Commands.add("refundCallTest", (requestBody, data, globalState) => { + const { + Configs: configs = {}, + Request: reqData, + Response: resData, + } = data || {}; - const payment_id = globalState.get("paymentID"); + const payment_id = globalState.get("paymentID"); - // we only need this to set the delay. We don't need the return value - execConfig(validateConfig(configs)); + // we only need this to set the delay. We don't need the return value + execConfig(validateConfig(configs)); - requestBody.amount = refund_amount; - requestBody.payment_id = payment_id; + requestBody.amount = reqData.amount; + requestBody.payment_id = payment_id; - cy.request({ - method: "POST", - url: `${globalState.get("baseUrl")}/refunds`, - headers: { - "Content-Type": "application/json", - "api-key": globalState.get("apiKey"), - }, - failOnStatusCode: false, - body: requestBody, - }).then((response) => { - logRequestId(response.headers["x-request-id"]); + cy.request({ + method: "POST", + url: `${globalState.get("baseUrl")}/refunds`, + headers: { + "Content-Type": "application/json", + "api-key": globalState.get("apiKey"), + }, + failOnStatusCode: false, + body: requestBody, + }).then((response) => { + logRequestId(response.headers["x-request-id"]); - cy.wrap(response).then(() => { - expect(response.headers["content-type"]).to.include("application/json"); - if (response.status === 200) { - globalState.set("refundId", response.body.refund_id); - for (const key in resData.body) { - expect(resData.body[key]).to.equal(response.body[key]); - } - expect(response.body.payment_id).to.equal(payment_id); - } else { - defaultErrorHandler(response, resData); + cy.wrap(response).then(() => { + expect(response.headers["content-type"]).to.include("application/json"); + if (response.status === 200) { + globalState.set("refundId", response.body.refund_id); + for (const key in resData.body) { + expect(resData.body[key]).to.equal(response.body[key]); } - }); + expect(response.body.payment_id).to.equal(payment_id); + } else { + defaultErrorHandler(response, resData); + } }); - } -); + }); +}); Cypress.Commands.add("syncRefundCallTest", (data, globalState) => { const { Response: resData } = data || {}; From fd8119782a5d78a4be4561b44d0f68f498fe25b9 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:11:01 +0530 Subject: [PATCH 084/133] fix(v2): Trait gating in v2 (#7223) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/core/routing/helpers.rs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index a08f80354bf..6a21dddc066 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -2,9 +2,11 @@ //! //! Functions that are used to perform the retrieval of merchant's //! routing dict, configs, defaults +use std::fmt::Debug; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use std::str::FromStr; -use std::{fmt::Debug, sync::Arc}; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use std::sync::Arc; use api_models::routing as routing_types; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -13,7 +15,7 @@ use common_utils::{ext_traits::Encode, id_type, types::keymanager::KeyManagerSta use diesel_models::configs; #[cfg(all(feature = "v1", feature = "dynamic_routing"))] use diesel_models::dynamic_routing_stats::DynamicRoutingStatsNew; -#[cfg(feature = "v1")] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use diesel_models::routing_algorithm; use error_stack::ResultExt; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] @@ -21,14 +23,16 @@ use external_services::grpc_client::dynamic_routing::{ contract_routing_client::ContractBasedDynamicRouting, success_rate_client::SuccessBasedDynamicRouting, }; -#[cfg(feature = "v1")] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use hyperswitch_domain_models::api::ApplicationResponse; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::logger; -#[cfg(any(feature = "dynamic_routing", feature = "v1"))] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use router_env::{instrument, tracing}; use rustc_hash::FxHashSet; -use storage_impl::redis::cache::{self, Cacheable}; +use storage_impl::redis::cache; +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] +use storage_impl::redis::cache::Cacheable; #[cfg(all(feature = "dynamic_routing", feature = "v1"))] use crate::db::errors::StorageErrorExt; @@ -41,7 +45,7 @@ use crate::{ types::{domain, storage}, utils::StringExt, }; -#[cfg(feature = "v1")] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] use crate::{core::metrics as core_metrics, types::transformers::ForeignInto}; pub const SUCCESS_BASED_DYNAMIC_ROUTING_ALGORITHM: &str = "Success rate based dynamic routing algorithm"; @@ -566,6 +570,7 @@ pub fn get_default_config_key( } } +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] #[async_trait::async_trait] pub trait DynamicRoutingCache { async fn get_cached_dynamic_routing_config_for_profile( @@ -584,6 +589,7 @@ pub trait DynamicRoutingCache { Fut: futures::Future> + Send; } +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] #[async_trait::async_trait] impl DynamicRoutingCache for routing_types::SuccessBasedRoutingConfig { async fn get_cached_dynamic_routing_config_for_profile( @@ -620,6 +626,7 @@ impl DynamicRoutingCache for routing_types::SuccessBasedRoutingConfig { } } +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] #[async_trait::async_trait] impl DynamicRoutingCache for routing_types::ContractBasedRoutingConfig { async fn get_cached_dynamic_routing_config_for_profile( @@ -1502,7 +1509,7 @@ where Ok(ApplicationResponse::Json(updated_routing_record)) } -#[cfg(feature = "v1")] +#[cfg(all(feature = "dynamic_routing", feature = "v1"))] #[instrument(skip_all)] pub async fn default_specific_dynamic_routing_setup( state: &SessionState, From 40a36fd319ccdb495deb077005ffcaea9cdf2427 Mon Sep 17 00:00:00 2001 From: sweta-sharma <77436883+Sweta-Kumari-Sharma@users.noreply.github.com> Date: Wed, 12 Feb 2025 13:18:11 +0530 Subject: [PATCH 085/133] refactor(connector): [Adyen] Removed deprecated PMTs from Ayden (Giropay, Sofort) (#7100) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 4 +- config/deployments/integration_test.toml | 4 +- config/deployments/production.toml | 4 +- config/deployments/sandbox.toml | 4 +- config/development.toml | 4 +- config/docker_compose.toml | 4 +- .../connector_configs/toml/development.toml | 4 -- crates/connector_configs/toml/production.toml | 4 -- crates/connector_configs/toml/sandbox.toml | 4 -- crates/router/src/connector/adyen.rs | 6 +-- .../src/connector/adyen/transformers.rs | 43 +++++-------------- loadtest/config/development.toml | 4 +- 12 files changed, 26 insertions(+), 63 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 15012c92405..05a0e069a16 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -463,11 +463,11 @@ bank_debit.becs = { connector_list = "gocardless" } bank_debit.bacs = { connector_list = "adyen" } # Mandate supported payment method type and connector for bank_debit bank_debit.sepa = { connector_list = "gocardless,adyen" } # Mandate supported payment method type and connector for bank_debit bank_redirect.ideal = { connector_list = "stripe,adyen,globalpay" } # Mandate supported payment method type and connector for bank_redirect -bank_redirect.sofort = { connector_list = "stripe,adyen,globalpay" } +bank_redirect.sofort = { connector_list = "stripe,globalpay" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.samsung_pay = { connector_list = "cybersource" } wallet.google_pay = { connector_list = "bankofamerica" } -bank_redirect.giropay = { connector_list = "adyen,globalpay" } +bank_redirect.giropay = { connector_list = "globalpay" } [mandates.update_mandate_supported] diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index bc46dda1fcb..e5badf2cec7 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -184,8 +184,8 @@ wallet.twint.connector_list = "adyen" wallet.vipps.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,globalpay" +bank_redirect.giropay.connector_list = "globalpay,multisafepay,nexinets" bank_redirect.bancontact_card.connector_list="adyen,stripe" bank_redirect.trustly.connector_list="adyen" bank_redirect.open_banking_uk.connector_list="adyen" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 07ca70b9af4..32638988f6d 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -184,8 +184,8 @@ wallet.twint.connector_list = "adyen" wallet.vipps.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,globalpay" +bank_redirect.giropay.connector_list = "globalpay,multisafepay,nexinets" bank_redirect.bancontact_card.connector_list="adyen,stripe" bank_redirect.trustly.connector_list="adyen" bank_redirect.open_banking_uk.connector_list="adyen" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index b044641f881..f91cba9d992 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -184,8 +184,8 @@ wallet.twint.connector_list = "adyen" wallet.vipps.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,globalpay" +bank_redirect.giropay.connector_list = "globalpay,multisafepay,nexinets" bank_redirect.bancontact_card.connector_list="adyen,stripe" bank_redirect.trustly.connector_list="adyen" bank_redirect.open_banking_uk.connector_list="adyen" diff --git a/config/development.toml b/config/development.toml index 0cc3193a292..c2f284bea2c 100644 --- a/config/development.toml +++ b/config/development.toml @@ -698,8 +698,8 @@ wallet.twint.connector_list = "adyen" wallet.vipps.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,globalpay" +bank_redirect.giropay.connector_list = "globalpay,multisafepay,nexinets" bank_redirect.bancontact_card.connector_list = "adyen,stripe" bank_redirect.trustly.connector_list = "adyen" bank_redirect.open_banking_uk.connector_list = "adyen" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index cf5d83e4da2..83e64ddc57a 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -539,8 +539,8 @@ bank_debit.becs = { connector_list = "gocardless" } bank_debit.bacs = { connector_list = "adyen" } bank_debit.sepa = { connector_list = "gocardless,adyen" } bank_redirect.ideal = { connector_list = "stripe,adyen,globalpay" } -bank_redirect.sofort = { connector_list = "stripe,adyen,globalpay" } -bank_redirect.giropay = { connector_list = "adyen,globalpay" } +bank_redirect.sofort = { connector_list = "stripe,globalpay" } +bank_redirect.giropay = { connector_list = "globalpay" } [mandates.update_mandate_supported] card.credit = { connector_list = "cybersource" } diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 8a0b2038c38..72272b425d6 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -119,10 +119,6 @@ merchant_secret="Source verification key" payment_method_type = "sepa" [[adyen.bank_redirect]] payment_method_type = "ideal" -[[adyen.bank_redirect]] - payment_method_type = "giropay" -[[adyen.bank_redirect]] - payment_method_type = "sofort" [[adyen.bank_redirect]] payment_method_type = "eps" [[adyen.bank_redirect]] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 63cf2756fa7..13f0409ebf1 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -106,10 +106,6 @@ merchant_secret="Source verification key" payment_method_type = "bacs" [[adyen.bank_redirect]] payment_method_type = "ideal" -[[adyen.bank_redirect]] - payment_method_type = "giropay" -[[adyen.bank_redirect]] - payment_method_type = "sofort" [[adyen.bank_redirect]] payment_method_type = "eps" [[adyen.wallet]] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 4c28f29550b..70895d48c8d 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -119,10 +119,6 @@ merchant_secret="Source verification key" payment_method_type = "sepa" [[adyen.bank_redirect]] payment_method_type = "ideal" -[[adyen.bank_redirect]] - payment_method_type = "giropay" -[[adyen.bank_redirect]] - payment_method_type = "sofort" [[adyen.bank_redirect]] payment_method_type = "eps" [[adyen.bank_redirect]] diff --git a/crates/router/src/connector/adyen.rs b/crates/router/src/connector/adyen.rs index 91e65759573..7067592771f 100644 --- a/crates/router/src/connector/adyen.rs +++ b/crates/router/src/connector/adyen.rs @@ -192,8 +192,6 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::Alfamart | PaymentMethodType::Indomaret | PaymentMethodType::FamilyMart - | PaymentMethodType::Sofort - | PaymentMethodType::Giropay | PaymentMethodType::Seicomart | PaymentMethodType::PayEasy | PaymentMethodType::MiniStop @@ -228,10 +226,12 @@ impl ConnectorValidation for Adyen { | PaymentMethodType::Pse | PaymentMethodType::LocalBankTransfer | PaymentMethodType::Efecty + | PaymentMethodType::Giropay | PaymentMethodType::PagoEfectivo | PaymentMethodType::PromptPay | PaymentMethodType::RedCompra | PaymentMethodType::RedPagos + | PaymentMethodType::Sofort | PaymentMethodType::CryptoCurrency | PaymentMethodType::Evoucher | PaymentMethodType::Cashapp @@ -274,9 +274,7 @@ impl ConnectorValidation for Adyen { PaymentMethodDataType::VippsRedirect, PaymentMethodDataType::KlarnaRedirect, PaymentMethodDataType::Ideal, - PaymentMethodDataType::Sofort, PaymentMethodDataType::OpenBankingUk, - PaymentMethodDataType::Giropay, PaymentMethodDataType::Trustly, PaymentMethodDataType::BancontactCard, PaymentMethodDataType::AchBankDebit, diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 69749500dab..465730cca64 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -551,7 +551,6 @@ pub enum AdyenPaymentMethod<'a> { Eps(Box>), #[serde(rename = "gcash")] Gcash(Box), - Giropay(Box), Gpay(Box), #[serde(rename = "gopay_wallet")] GoPay(Box), @@ -585,8 +584,6 @@ pub enum AdyenPaymentMethod<'a> { PayBright, #[serde(rename = "doku_permata_lite_atm")] PermataBankTransfer(Box), - #[serde(rename = "directEbanking")] - Sofort, #[serde(rename = "trustly")] Trustly, #[serde(rename = "walley")] @@ -1310,7 +1307,6 @@ pub enum PaymentType { Dana, Eps, Gcash, - Giropay, Googlepay, #[serde(rename = "gopay_wallet")] GoPay, @@ -1346,8 +1342,6 @@ pub enum PaymentType { PayBright, Paypal, Scheme, - #[serde(rename = "directEbanking")] - Sofort, #[serde(rename = "networkToken")] NetworkToken, Trustly, @@ -2075,13 +2069,11 @@ impl TryFrom<&storage_enums::PaymentMethodType> for PaymentType { | storage_enums::PaymentMethodType::BancontactCard | storage_enums::PaymentMethodType::Blik | storage_enums::PaymentMethodType::Eps - | storage_enums::PaymentMethodType::Giropay | storage_enums::PaymentMethodType::Ideal | storage_enums::PaymentMethodType::OnlineBankingCzechRepublic | storage_enums::PaymentMethodType::OnlineBankingFinland | storage_enums::PaymentMethodType::OnlineBankingPoland | storage_enums::PaymentMethodType::OnlineBankingSlovakia - | storage_enums::PaymentMethodType::Sofort | storage_enums::PaymentMethodType::Trustly | storage_enums::PaymentMethodType::GooglePay | storage_enums::PaymentMethodType::AliPay @@ -2458,11 +2450,6 @@ impl ), }), )), - domain::BankRedirectData::Giropay { .. } => { - Ok(AdyenPaymentMethod::Giropay(Box::new(PmdForPaymentType { - payment_type: PaymentType::Giropay, - }))) - } domain::BankRedirectData::Ideal { bank_name, .. } => { let issuer = if test_mode.unwrap_or(true) { Some( @@ -2533,11 +2520,12 @@ impl }, })), ), - domain::BankRedirectData::Sofort { .. } => Ok(AdyenPaymentMethod::Sofort), domain::BankRedirectData::Trustly { .. } => Ok(AdyenPaymentMethod::Trustly), - domain::BankRedirectData::Interac { .. } + domain::BankRedirectData::Giropay { .. } + | domain::BankRedirectData::Interac { .. } | domain::BankRedirectData::LocalBankRedirect {} - | domain::BankRedirectData::Przelewy24 { .. } => { + | domain::BankRedirectData::Przelewy24 { .. } + | domain::BankRedirectData::Sofort { .. } => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Adyen"), ) @@ -3220,20 +3208,13 @@ fn get_redirect_extra_details( ) -> errors::CustomResult<(Option, Option), errors::ConnectorError> { match item.request.payment_method_data { - domain::PaymentMethodData::BankRedirect(ref redirect_data) => match redirect_data { - domain::BankRedirectData::Sofort { - preferred_language, .. - } => { - let country = item.get_optional_billing_country(); - Ok((preferred_language.clone(), country)) - } + domain::PaymentMethodData::BankRedirect( domain::BankRedirectData::Trustly { .. } - | domain::BankRedirectData::OpenBankingUk { .. } => { - let country = item.get_optional_billing_country(); - Ok((None, country)) - } - _ => Ok((None, None)), - }, + | domain::BankRedirectData::OpenBankingUk { .. }, + ) => { + let country = item.get_optional_billing_country(); + Ok((None, country)) + } _ => Ok((None, None)), } } @@ -4074,7 +4055,6 @@ pub fn get_wait_screen_metadata( | PaymentType::Dana | PaymentType::Eps | PaymentType::Gcash - | PaymentType::Giropay | PaymentType::Googlepay | PaymentType::GoPay | PaymentType::Ideal @@ -4094,7 +4074,6 @@ pub fn get_wait_screen_metadata( | PaymentType::PayBright | PaymentType::Paypal | PaymentType::Scheme - | PaymentType::Sofort | PaymentType::NetworkToken | PaymentType::Trustly | PaymentType::TouchNGo @@ -4191,7 +4170,6 @@ pub fn get_present_to_shopper_metadata( | PaymentType::Dana | PaymentType::Eps | PaymentType::Gcash - | PaymentType::Giropay | PaymentType::Googlepay | PaymentType::GoPay | PaymentType::Ideal @@ -4213,7 +4191,6 @@ pub fn get_present_to_shopper_metadata( | PaymentType::PayBright | PaymentType::Paypal | PaymentType::Scheme - | PaymentType::Sofort | PaymentType::NetworkToken | PaymentType::Trustly | PaymentType::TouchNGo diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index c2cc8833f87..a57007f0d3a 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -355,8 +355,8 @@ wallet.twint.connector_list = "adyen" wallet.vipps.connector_list = "adyen" bank_redirect.ideal.connector_list = "stripe,adyen,globalpay,multisafepay,nexinets" -bank_redirect.sofort.connector_list = "stripe,adyen,globalpay" -bank_redirect.giropay.connector_list = "adyen,globalpay,multisafepay,nexinets" +bank_redirect.sofort.connector_list = "stripe,globalpay" +bank_redirect.giropay.connector_list = "globalpay,multisafepay,nexinets" bank_redirect.bancontact_card.connector_list="adyen,stripe" bank_redirect.trustly.connector_list="adyen" bank_redirect.open_banking_uk.connector_list="adyen" From fa09db1534884037947c6d488e33a3ce600c2a0c Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 12 Feb 2025 13:18:38 +0530 Subject: [PATCH 086/133] refactor(schema): add a new column for storing large connector transaction IDs (#7017) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/common_utils/src/types.rs | 8 +- crates/diesel_models/src/capture.rs | 16 +- crates/diesel_models/src/kv.rs | 2 +- crates/diesel_models/src/payment_attempt.rs | 74 +- crates/diesel_models/src/query/capture.rs | 2 +- crates/diesel_models/src/refund.rs | 45 +- crates/diesel_models/src/schema.rs | 4 + crates/diesel_models/src/schema_v2.rs | 12 +- crates/diesel_models/src/user/sample_data.rs | 2 +- .../src/connectors/worldpay/requests.rs | 6 +- .../src/connectors/worldpay/transformers.rs | 10 +- .../src/payments/payment_attempt.rs | 6 +- .../payment_connector_required_fields.rs | 9 + .../payments/operations/payment_response.rs | 4 +- .../router/src/core/payments/transformers.rs | 4 +- crates/router/src/core/refunds.rs | 32 +- crates/router/src/core/webhooks/incoming.rs | 2 +- crates/router/src/db/capture.rs | 4 +- crates/router/src/db/refund.rs | 18 +- .../src/types/storage/payment_attempt.rs | 2 + crates/router/src/utils/user/sample_data.rs | 10 +- .../src/payments/payment_attempt.rs | 6 +- .../cypress/e2e/configs/Payment/WorldPay.js | 800 ++++++++++++++++++ .../down.sql | 8 + .../up.sql | 11 + .../2024-08-28-081721_add_v2_columns/up.sql | 2 +- .../down.sql | 2 +- .../up.sql | 8 + 28 files changed, 987 insertions(+), 122 deletions(-) create mode 100644 migrations/2025-01-09-135057_add_processor_transaction_data/down.sql create mode 100644 migrations/2025-01-09-135057_add_processor_transaction_data/up.sql rename v2_migrations/{2024-11-08-081847_drop_v1_columns => 2025-01-13-081847_drop_v1_columns}/down.sql (98%) rename v2_migrations/{2024-11-08-081847_drop_v1_columns => 2025-01-13-081847_drop_v1_columns}/up.sql (92%) diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 4e88d5c0fde..0377a3e8183 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -1420,7 +1420,7 @@ crate::impl_to_sql_from_sql_json!(BrowserInformation); /// In case connector's use an identifier whose length exceeds 128 characters, /// the hash value of such identifiers will be stored as connector_transaction_id. /// The actual connector's identifier will be stored in a separate column - -/// connector_transaction_data or something with a similar name. +/// processor_transaction_data or something with a similar name. #[derive(Debug, Clone, PartialEq, Eq, serde::Deserialize, serde::Serialize, AsExpression)] #[diesel(sql_type = sql_types::Text)] pub enum ConnectorTransactionId { @@ -1438,7 +1438,7 @@ impl ConnectorTransactionId { } } - /// Implementation for forming ConnectorTransactionId and an optional string to be used for connector_transaction_id and connector_transaction_data + /// Implementation for forming ConnectorTransactionId and an optional string to be used for connector_transaction_id and processor_transaction_data pub fn form_id_and_data(src: String) -> (Self, Option) { let txn_id = Self::from(src.clone()); match txn_id { @@ -1456,10 +1456,10 @@ impl ConnectorTransactionId { (Self::TxnId(id), _) => Ok(id), (Self::HashedData(_), Some(id)) => Ok(id), (Self::HashedData(id), None) => Err(report!(ValidationError::InvalidValue { - message: "connector_transaction_data is empty for HashedData variant".to_string(), + message: "processor_transaction_data is empty for HashedData variant".to_string(), }) .attach_printable(format!( - "connector_transaction_data is empty for connector_transaction_id {}", + "processor_transaction_data is empty for connector_transaction_id {}", id ))), } diff --git a/crates/diesel_models/src/capture.rs b/crates/diesel_models/src/capture.rs index d6272c34060..367382b98bf 100644 --- a/crates/diesel_models/src/capture.rs +++ b/crates/diesel_models/src/capture.rs @@ -30,7 +30,9 @@ pub struct Capture { pub capture_sequence: i16, // reference to the capture at connector side pub connector_response_reference_id: Option, + /// INFO: This field is deprecated and replaced by processor_capture_data pub connector_capture_data: Option, + pub processor_capture_data: Option, } #[derive(Clone, Debug, Insertable, router_derive::DebugAsDisplay, Serialize, Deserialize)] @@ -55,7 +57,9 @@ pub struct CaptureNew { pub connector_capture_id: Option, pub capture_sequence: i16, pub connector_response_reference_id: Option, + /// INFO: This field is deprecated and replaced by processor_capture_data pub connector_capture_data: Option, + pub processor_capture_data: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -64,7 +68,7 @@ pub enum CaptureUpdate { status: storage_enums::CaptureStatus, connector_capture_id: Option, connector_response_reference_id: Option, - connector_capture_data: Option, + processor_capture_data: Option, }, ErrorUpdate { status: storage_enums::CaptureStatus, @@ -84,7 +88,7 @@ pub struct CaptureUpdateInternal { pub modified_at: Option, pub connector_capture_id: Option, pub connector_response_reference_id: Option, - pub connector_capture_data: Option, + pub processor_capture_data: Option, } impl CaptureUpdate { @@ -97,7 +101,7 @@ impl CaptureUpdate { modified_at: _, connector_capture_id, connector_response_reference_id, - connector_capture_data, + processor_capture_data, } = self.into(); Capture { status: status.unwrap_or(source.status), @@ -108,7 +112,7 @@ impl CaptureUpdate { connector_capture_id: connector_capture_id.or(source.connector_capture_id), connector_response_reference_id: connector_response_reference_id .or(source.connector_response_reference_id), - connector_capture_data: connector_capture_data.or(source.connector_capture_data), + processor_capture_data: processor_capture_data.or(source.processor_capture_data), ..source } } @@ -122,13 +126,13 @@ impl From for CaptureUpdateInternal { status, connector_capture_id: connector_transaction_id, connector_response_reference_id, - connector_capture_data, + processor_capture_data, } => Self { status: Some(status), connector_capture_id: connector_transaction_id, modified_at: now, connector_response_reference_id, - connector_capture_data, + processor_capture_data, ..Self::default() }, CaptureUpdate::ErrorUpdate { diff --git a/crates/diesel_models/src/kv.rs b/crates/diesel_models/src/kv.rs index 94dcf00f755..54e60891532 100644 --- a/crates/diesel_models/src/kv.rs +++ b/crates/diesel_models/src/kv.rs @@ -227,7 +227,7 @@ pub enum Insertable { pub enum Updateable { PaymentIntentUpdate(Box), PaymentAttemptUpdate(Box), - RefundUpdate(RefundUpdateMems), + RefundUpdate(Box), CustomerUpdate(CustomerUpdateMems), AddressUpdate(Box), PayoutsUpdate(PayoutsUpdateMems), diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index eab95f81316..b3b3f44f3c9 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -171,8 +171,10 @@ pub struct PaymentAttempt { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, + /// INFO: This field is deprecated and replaced by processor_transaction_data pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub processor_transaction_data: Option, pub card_discovery: Option, pub charges: Option, } @@ -183,7 +185,7 @@ impl ConnectorTransactionIdTrait for PaymentAttempt { match self .connector_transaction_id .as_ref() - .map(|txn_id| txn_id.get_txn_id(self.connector_transaction_data.as_ref())) + .map(|txn_id| txn_id.get_txn_id(self.processor_transaction_data.as_ref())) .transpose() { Ok(txn_id) => txn_id, @@ -871,8 +873,8 @@ pub struct PaymentAttemptUpdateInternal { pub card_network: Option, pub shipping_cost: Option, pub order_tax_amount: Option, - pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub processor_transaction_data: Option, pub card_discovery: Option, pub charges: Option, } @@ -1055,7 +1057,7 @@ impl PaymentAttemptUpdate { card_network, shipping_cost, order_tax_amount, - connector_transaction_data, + processor_transaction_data, connector_mandate_detail, card_discovery, charges, @@ -1113,8 +1115,8 @@ impl PaymentAttemptUpdate { card_network: card_network.or(source.card_network), shipping_cost: shipping_cost.or(source.shipping_cost), order_tax_amount: order_tax_amount.or(source.order_tax_amount), - connector_transaction_data: connector_transaction_data - .or(source.connector_transaction_data), + processor_transaction_data: processor_transaction_data + .or(source.processor_transaction_data), connector_mandate_detail: connector_mandate_detail.or(source.connector_mandate_detail), card_discovery: card_discovery.or(source.card_discovery), charges: charges.or(source.charges), @@ -1483,7 +1485,7 @@ impl From for PaymentAttemptUpdateInternal { // card_network: None, // shipping_cost: None, // order_tax_amount: None, - // connector_transaction_data: None, + // processor_transaction_data: None, // connector_mandate_detail, // }, // PaymentAttemptUpdate::ConnectorMandateDetailUpdate { @@ -2167,7 +2169,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2224,7 +2226,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2313,7 +2315,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost, order_tax_amount, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail, card_discovery, charges: None, @@ -2371,7 +2373,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2430,7 +2432,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2489,7 +2491,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2546,7 +2548,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail, card_discovery: None, charges: None, @@ -2603,7 +2605,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2631,7 +2633,7 @@ impl From for PaymentAttemptUpdateInternal { connector_mandate_detail, charges, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -2657,7 +2659,9 @@ impl From for PaymentAttemptUpdateInternal { unified_code, unified_message, payment_method_data, - connector_transaction_data, + processor_transaction_data, + connector_mandate_detail, + charges, amount: None, net_amount: None, currency: None, @@ -2686,9 +2690,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_mandate_detail, card_discovery: None, - charges, } } PaymentAttemptUpdate::ErrorUpdate { @@ -2705,7 +2707,7 @@ impl From for PaymentAttemptUpdateInternal { payment_method_data, authentication_type, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -2724,7 +2726,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, payment_method_data, authentication_type, - connector_transaction_data, + processor_transaction_data, amount: None, net_amount: None, currency: None, @@ -2814,7 +2816,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2877,7 +2879,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -2893,7 +2895,7 @@ impl From for PaymentAttemptUpdateInternal { connector_response_reference_id, updated_by, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -2909,7 +2911,7 @@ impl From for PaymentAttemptUpdateInternal { error_reason, connector_response_reference_id, updated_by, - connector_transaction_data, + processor_transaction_data, amount: None, net_amount: None, currency: None, @@ -2962,7 +2964,7 @@ impl From for PaymentAttemptUpdateInternal { connector_response_reference_id, updated_by, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -2976,7 +2978,7 @@ impl From for PaymentAttemptUpdateInternal { connector_transaction_id, connector_response_reference_id, updated_by, - connector_transaction_data, + processor_transaction_data, amount: None, net_amount: None, currency: None, @@ -3075,7 +3077,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -3133,7 +3135,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -3146,7 +3148,7 @@ impl From for PaymentAttemptUpdateInternal { updated_by, charges, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -3158,7 +3160,8 @@ impl From for PaymentAttemptUpdateInternal { connector: connector.map(Some), modified_at: common_utils::date_time::now(), updated_by, - connector_transaction_data, + processor_transaction_data, + charges, amount: None, net_amount: None, currency: None, @@ -3203,7 +3206,6 @@ impl From for PaymentAttemptUpdateInternal { order_tax_amount: None, connector_mandate_detail: None, card_discovery: None, - charges, } } PaymentAttemptUpdate::IncrementalAuthorizationAmountUpdate { @@ -3258,7 +3260,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -3318,7 +3320,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, @@ -3333,7 +3335,7 @@ impl From for PaymentAttemptUpdateInternal { unified_message, connector_transaction_id, } => { - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -3348,7 +3350,7 @@ impl From for PaymentAttemptUpdateInternal { unified_code: unified_code.map(Some), unified_message: unified_message.map(Some), connector_transaction_id, - connector_transaction_data, + processor_transaction_data, amount: None, net_amount: None, currency: None, @@ -3445,7 +3447,7 @@ impl From for PaymentAttemptUpdateInternal { card_network: None, shipping_cost: None, order_tax_amount: None, - connector_transaction_data: None, + processor_transaction_data: None, connector_mandate_detail: None, card_discovery: None, charges: None, diff --git a/crates/diesel_models/src/query/capture.rs b/crates/diesel_models/src/query/capture.rs index 7a32e410961..3f02948d2a4 100644 --- a/crates/diesel_models/src/query/capture.rs +++ b/crates/diesel_models/src/query/capture.rs @@ -74,7 +74,7 @@ impl ConnectorTransactionIdTrait for Capture { match self .connector_capture_id .as_ref() - .map(|capture_id| capture_id.get_txn_id(self.connector_capture_data.as_ref())) + .map(|capture_id| capture_id.get_txn_id(self.processor_capture_data.as_ref())) .transpose() { Ok(capture_id) => capture_id, diff --git a/crates/diesel_models/src/refund.rs b/crates/diesel_models/src/refund.rs index b929481d64d..f19c3252c60 100644 --- a/crates/diesel_models/src/refund.rs +++ b/crates/diesel_models/src/refund.rs @@ -51,11 +51,15 @@ pub struct Refund { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, + /// INFO: This field is deprecated and replaced by processor_refund_data pub connector_refund_data: Option, + /// INFO: This field is deprecated and replaced by processor_transaction_data pub connector_transaction_data: Option, pub split_refunds: Option, pub unified_code: Option, pub unified_message: Option, + pub processor_refund_data: Option, + pub processor_transaction_data: Option, } #[derive( @@ -99,9 +103,9 @@ pub struct RefundNew { pub merchant_connector_id: Option, pub charges: Option, pub organization_id: common_utils::id_type::OrganizationId, - pub connector_refund_data: Option, - pub connector_transaction_data: Option, pub split_refunds: Option, + pub processor_refund_data: Option, + pub processor_transaction_data: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] @@ -113,7 +117,7 @@ pub enum RefundUpdate { refund_error_message: Option, refund_arn: String, updated_by: String, - connector_refund_data: Option, + processor_refund_data: Option, }, MetadataAndReasonUpdate { metadata: Option, @@ -125,7 +129,7 @@ pub enum RefundUpdate { sent_to_gateway: bool, refund_status: storage_enums::RefundStatus, updated_by: String, - connector_refund_data: Option, + processor_refund_data: Option, }, ErrorUpdate { refund_status: Option, @@ -133,7 +137,7 @@ pub enum RefundUpdate { refund_error_code: Option, updated_by: String, connector_refund_id: Option, - connector_refund_data: Option, + processor_refund_data: Option, unified_code: Option, unified_message: Option, }, @@ -158,7 +162,7 @@ pub struct RefundUpdateInternal { refund_error_code: Option, updated_by: String, modified_at: PrimitiveDateTime, - connector_refund_data: Option, + processor_refund_data: Option, unified_code: Option, unified_message: Option, } @@ -176,7 +180,7 @@ impl RefundUpdateInternal { refund_error_code: self.refund_error_code, updated_by: self.updated_by, modified_at: self.modified_at, - connector_refund_data: self.connector_refund_data, + processor_refund_data: self.processor_refund_data, unified_code: self.unified_code, unified_message: self.unified_message, ..source @@ -194,7 +198,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn, updated_by, - connector_refund_data, + processor_refund_data, } => Self { connector_refund_id: Some(connector_refund_id), refund_status: Some(refund_status), @@ -202,7 +206,7 @@ impl From for RefundUpdateInternal { refund_error_message, refund_arn: Some(refund_arn), updated_by, - connector_refund_data, + processor_refund_data, metadata: None, refund_reason: None, refund_error_code: None, @@ -225,7 +229,7 @@ impl From for RefundUpdateInternal { refund_arn: None, refund_error_code: None, modified_at: common_utils::date_time::now(), - connector_refund_data: None, + processor_refund_data: None, unified_code: None, unified_message: None, }, @@ -234,13 +238,13 @@ impl From for RefundUpdateInternal { sent_to_gateway, refund_status, updated_by, - connector_refund_data, + processor_refund_data, } => Self { connector_refund_id, sent_to_gateway: Some(sent_to_gateway), refund_status: Some(refund_status), updated_by, - connector_refund_data, + processor_refund_data, refund_error_message: None, refund_arn: None, metadata: None, @@ -258,14 +262,14 @@ impl From for RefundUpdateInternal { unified_message, updated_by, connector_refund_id, - connector_refund_data, + processor_refund_data, } => Self { refund_status, refund_error_message, refund_error_code, updated_by, connector_refund_id, - connector_refund_data, + processor_refund_data, sent_to_gateway: None, refund_arn: None, metadata: None, @@ -290,7 +294,7 @@ impl From for RefundUpdateInternal { metadata: None, refund_reason: None, modified_at: common_utils::date_time::now(), - connector_refund_data: None, + processor_refund_data: None, unified_code: None, unified_message: None, }, @@ -311,7 +315,7 @@ impl RefundUpdate { refund_error_code, updated_by, modified_at: _, - connector_refund_data, + processor_refund_data, unified_code, unified_message, } = self.into(); @@ -326,7 +330,7 @@ impl RefundUpdate { refund_reason: refund_reason.or(source.refund_reason), updated_by, modified_at: common_utils::date_time::now(), - connector_refund_data: connector_refund_data.or(source.connector_refund_data), + processor_refund_data: processor_refund_data.or(source.processor_refund_data), unified_code: unified_code.or(source.unified_code), unified_message: unified_message.or(source.unified_message), ..source @@ -340,7 +344,7 @@ pub struct RefundCoreWorkflow { pub connector_transaction_id: ConnectorTransactionId, pub merchant_id: common_utils::id_type::MerchantId, pub payment_id: common_utils::id_type::PaymentId, - pub connector_transaction_data: Option, + pub processor_transaction_data: Option, } #[cfg(feature = "v1")] @@ -358,7 +362,7 @@ impl ConnectorTransactionIdTrait for Refund { match self .connector_refund_id .as_ref() - .map(|refund_id| refund_id.get_txn_id(self.connector_refund_data.as_ref())) + .map(|refund_id| refund_id.get_txn_id(self.processor_refund_data.as_ref())) .transpose() { Ok(refund_id) => refund_id, @@ -374,7 +378,7 @@ impl ConnectorTransactionIdTrait for Refund { fn get_connector_transaction_id(&self) -> &String { match self .connector_transaction_id - .get_txn_id(self.connector_transaction_data.as_ref()) + .get_txn_id(self.processor_transaction_data.as_ref()) { Ok(txn_id) => txn_id, @@ -418,6 +422,7 @@ mod tests { "connector_transaction_data": null "unified_code": null, "unified_message": null, + "processor_transaction_data": null, }"#; let deserialized = serde_json::from_str::(serialized_refund); diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index bbde92ea040..2e8219c5ab4 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -271,6 +271,7 @@ diesel::table! { connector_response_reference_id -> Nullable, #[max_length = 512] connector_capture_data -> Nullable, + processor_capture_data -> Nullable, } } @@ -909,6 +910,7 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, connector_mandate_detail -> Nullable, + processor_transaction_data -> Nullable, card_discovery -> Nullable, charges -> Nullable, } @@ -1261,6 +1263,8 @@ diesel::table! { unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, + processor_refund_data -> Nullable, + processor_transaction_data -> Nullable, } } diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index d17d4f8478a..ab2195526f5 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -278,8 +278,7 @@ diesel::table! { capture_sequence -> Int2, #[max_length = 128] connector_response_reference_id -> Nullable, - #[max_length = 512] - connector_capture_data -> Nullable, + processor_capture_data -> Nullable, } } @@ -872,8 +871,7 @@ diesel::table! { tax_on_surcharge -> Nullable, payment_method_billing_address -> Nullable, redirection_data -> Nullable, - #[max_length = 512] - connector_payment_data -> Nullable, + connector_payment_data -> Nullable, connector_token_details -> Nullable, #[max_length = 64] id -> Varchar, @@ -1198,15 +1196,13 @@ diesel::table! { charges -> Nullable, #[max_length = 32] organization_id -> Varchar, - #[max_length = 512] - connector_refund_data -> Nullable, - #[max_length = 512] - connector_transaction_data -> Nullable, split_refunds -> Nullable, #[max_length = 255] unified_code -> Nullable, #[max_length = 1024] unified_message -> Nullable, + processor_refund_data -> Nullable, + processor_transaction_data -> Nullable, } } diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index c063545d81f..27085424083 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -201,7 +201,7 @@ pub struct PaymentAttemptBatchNew { pub organization_id: common_utils::id_type::OrganizationId, pub shipping_cost: Option, pub order_tax_amount: Option, - pub connector_transaction_data: Option, + pub processor_transaction_data: Option, pub connector_mandate_detail: Option, pub card_discovery: Option, } diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs index 6e19c20edab..a43ffb7cfa1 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs @@ -149,14 +149,12 @@ pub struct ExpiryDate { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BillingAddress { - #[serde(skip_serializing_if = "Option::is_none")] - pub address1: Option>, + pub address1: Secret, #[serde(skip_serializing_if = "Option::is_none")] pub address2: Option>, #[serde(skip_serializing_if = "Option::is_none")] pub address3: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - pub city: Option, + pub city: String, #[serde(skip_serializing_if = "Option::is_none")] pub state: Option>, pub postal_code: Secret, diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index 28cbca418a3..65c884601de 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -90,10 +90,16 @@ fn fetch_payment_instrument( billing_address.and_then(|addr| addr.address.clone()) { Some(BillingAddress { - address1: address.line1, + address1: address.line1.get_required_value("line1").change_context( + errors::ConnectorError::MissingRequiredField { + field_name: "line1", + }, + )?, address2: address.line2, address3: address.line3, - city: address.city, + city: address.city.get_required_value("city").change_context( + errors::ConnectorError::MissingRequiredField { field_name: "city" }, + )?, state: address.state, postal_code: address.zip.get_required_value("zip").change_context( errors::ConnectorError::MissingRequiredField { field_name: "zip" }, diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index e641ee63d27..133d04ceb69 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -1512,7 +1512,7 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()); - let (connector_transaction_id, connector_transaction_data) = self + let (connector_transaction_id, processor_transaction_data) = self .connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -1579,12 +1579,14 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: self.profile_id, organization_id: self.organization_id, card_network, - connector_transaction_data, order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, + // Below fields are deprecated. Please add any new fields above this line. + connector_transaction_data: None, }) } diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index c5b9b9a0430..a9f5ae59493 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -13514,5 +13514,14 @@ pub fn get_worldpay_billing_required_fields() -> HashMap( .multiple_capture_data { Some(multiple_capture_data) => { - let (connector_capture_id, connector_capture_data) = + let (connector_capture_id, processor_capture_data) = match resource_id { types::ResponseId::NoResponseId => (None, None), types::ResponseId::ConnectorTransactionId(id) @@ -1679,7 +1679,7 @@ async fn payment_response_update_tracker( )?, connector_capture_id: connector_capture_id.clone(), connector_response_reference_id, - connector_capture_data: connector_capture_data.clone(), + processor_capture_data: processor_capture_data.clone(), }; let capture_update_list = vec![( multiple_capture_data.get_latest_capture().clone(), diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 1f6e03a0dec..ebf837792dc 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -3847,7 +3847,7 @@ impl ForeignTryFrom for storage::CaptureUpdate { connector_response_reference_id, .. } => { - let (connector_capture_id, connector_capture_data) = match resource_id { + let (connector_capture_id, processor_capture_data) = match resource_id { types::ResponseId::EncodedData(_) | types::ResponseId::NoResponseId => { (None, None) } @@ -3861,7 +3861,7 @@ impl ForeignTryFrom for storage::CaptureUpdate { status: enums::CaptureStatus::foreign_try_from(status)?, connector_capture_id, connector_response_reference_id, - connector_capture_data, + processor_capture_data, }) } types::CaptureSyncResponse::Error { diff --git a/crates/router/src/core/refunds.rs b/crates/router/src/core/refunds.rs index 4d8cc195693..938bf1a8369 100644 --- a/crates/router/src/core/refunds.rs +++ b/crates/router/src/core/refunds.rs @@ -235,7 +235,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("NOT_IMPLEMENTED".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: None, - connector_refund_data: None, + processor_refund_data: None, unified_code: None, unified_message: None, }) @@ -249,7 +249,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("NOT_SUPPORTED".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: None, - connector_refund_data: None, + processor_refund_data: None, unified_code: None, unified_message: None, }) @@ -332,7 +332,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some(err.code), updated_by: storage_scheme.to_string(), connector_refund_id: None, - connector_refund_data: None, + processor_refund_data: None, unified_code: Some(unified_code), unified_message: Some(unified_message), } @@ -341,7 +341,7 @@ pub async fn trigger_refund_to_gateway( // match on connector integrity checks match router_data_res.integrity_check.clone() { Err(err) => { - let (refund_connector_transaction_id, connector_refund_data) = + let (refund_connector_transaction_id, processor_refund_data) = err.connector_transaction_id.map_or((None, None), |txn_id| { let (refund_id, refund_data) = ConnectorTransactionId::form_id_and_data(txn_id); @@ -363,7 +363,7 @@ pub async fn trigger_refund_to_gateway( refund_error_code: Some("IE".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, - connector_refund_data, + processor_refund_data, unified_code: None, unified_message: None, } @@ -378,7 +378,7 @@ pub async fn trigger_refund_to_gateway( )), ) } - let (connector_refund_id, connector_refund_data) = + let (connector_refund_id, processor_refund_data) = ConnectorTransactionId::form_id_and_data(response.connector_refund_id); storage::RefundUpdate::Update { connector_refund_id, @@ -387,7 +387,7 @@ pub async fn trigger_refund_to_gateway( refund_error_message: None, refund_arn: "".to_string(), updated_by: storage_scheme.to_string(), - connector_refund_data, + processor_refund_data, } } } @@ -677,7 +677,7 @@ pub async fn sync_refund_with_gateway( refund_error_code: Some(error_message.code), updated_by: storage_scheme.to_string(), connector_refund_id: None, - connector_refund_data: None, + processor_refund_data: None, unified_code: None, unified_message: None, } @@ -691,7 +691,7 @@ pub async fn sync_refund_with_gateway( ("merchant_id", merchant_account.get_id().clone()), ), ); - let (refund_connector_transaction_id, connector_refund_data) = err + let (refund_connector_transaction_id, processor_refund_data) = err .connector_transaction_id .map_or((None, None), |refund_id| { let (refund_id, refund_data) = @@ -707,13 +707,13 @@ pub async fn sync_refund_with_gateway( refund_error_code: Some("IE".to_string()), updated_by: storage_scheme.to_string(), connector_refund_id: refund_connector_transaction_id, - connector_refund_data, + processor_refund_data, unified_code: None, unified_message: None, } } Ok(()) => { - let (connector_refund_id, connector_refund_data) = + let (connector_refund_id, processor_refund_data) = ConnectorTransactionId::form_id_and_data(response.connector_refund_id); storage::RefundUpdate::Update { connector_refund_id, @@ -722,7 +722,7 @@ pub async fn sync_refund_with_gateway( refund_error_message: None, refund_arn: "".to_string(), updated_by: storage_scheme.to_string(), - connector_refund_data, + processor_refund_data, } } }, @@ -882,7 +882,7 @@ pub async fn validate_and_create_refund( .clone() .ok_or(errors::ApiErrorResponse::InternalServerError) .attach_printable("No connector populated in payment attempt")?; - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = ConnectorTransactionId::form_id_and_data(connector_transaction_id); let refund_create_req = storage::RefundNew { refund_id: refund_id.to_string(), @@ -912,8 +912,8 @@ pub async fn validate_and_create_refund( refund_arn: None, updated_by: Default::default(), organization_id: merchant_account.organization_id.clone(), - connector_refund_data: None, - connector_transaction_data, + processor_transaction_data, + processor_refund_data: None, }; let refund = match db @@ -1584,7 +1584,7 @@ pub fn refund_to_refund_core_workflow_model( connector_transaction_id: refund.connector_transaction_id.clone(), merchant_id: refund.merchant_id.clone(), payment_id: refund.payment_id.clone(), - connector_transaction_data: refund.connector_transaction_data.clone(), + processor_transaction_data: refund.processor_transaction_data.clone(), } } diff --git a/crates/router/src/core/webhooks/incoming.rs b/crates/router/src/core/webhooks/incoming.rs index 2c0d9b5b5c5..8b562978804 100644 --- a/crates/router/src/core/webhooks/incoming.rs +++ b/crates/router/src/core/webhooks/incoming.rs @@ -1023,7 +1023,7 @@ async fn refunds_incoming_webhook_flow( .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) .attach_printable("failed refund status mapping from event type")?, updated_by: merchant_account.storage_scheme.to_string(), - connector_refund_data: None, + processor_refund_data: None, }; db.update_refund( refund.to_owned(), diff --git a/crates/router/src/db/capture.rs b/crates/router/src/db/capture.rs index cbc27f918d4..a2e4a90d66d 100644 --- a/crates/router/src/db/capture.rs +++ b/crates/router/src/db/capture.rs @@ -198,7 +198,9 @@ impl CaptureInterface for MockDb { capture_sequence: capture.capture_sequence, connector_capture_id: capture.connector_capture_id, connector_response_reference_id: capture.connector_response_reference_id, - connector_capture_data: capture.connector_capture_data, + processor_capture_data: capture.processor_capture_data, + // Below fields are deprecated. Please add any new fields above this line. + connector_capture_data: None, }; captures.push(capture.clone()); Ok(capture) diff --git a/crates/router/src/db/refund.rs b/crates/router/src/db/refund.rs index e07821cd9a8..07bdfa3f77d 100644 --- a/crates/router/src/db/refund.rs +++ b/crates/router/src/db/refund.rs @@ -435,10 +435,13 @@ mod storage { charges: new.charges.clone(), split_refunds: new.split_refunds.clone(), organization_id: new.organization_id.clone(), - connector_refund_data: new.connector_refund_data.clone(), - connector_transaction_data: new.connector_transaction_data.clone(), unified_code: None, unified_message: None, + processor_refund_data: new.processor_refund_data.clone(), + processor_transaction_data: new.processor_transaction_data.clone(), + // Below fields are deprecated. Please add any new fields above this line. + connector_refund_data: None, + connector_transaction_data: None, }; let field = format!( @@ -620,12 +623,12 @@ mod storage { let redis_entry = kv::TypedSql { op: kv::DBOperation::Update { - updatable: Box::new(kv::Updateable::RefundUpdate( + updatable: Box::new(kv::Updateable::RefundUpdate(Box::new( kv::RefundUpdateMems { orig: this, update_data: refund, }, - )), + ))), }, }; @@ -932,10 +935,13 @@ impl RefundInterface for MockDb { charges: new.charges, split_refunds: new.split_refunds, organization_id: new.organization_id, - connector_refund_data: new.connector_refund_data, - connector_transaction_data: new.connector_transaction_data, unified_code: None, unified_message: None, + processor_refund_data: new.processor_refund_data.clone(), + processor_transaction_data: new.processor_transaction_data.clone(), + // Below fields are deprecated. Please add any new fields above this line. + connector_refund_data: None, + connector_transaction_data: None, }; refunds.push(refund.clone()); Ok(refund) diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index 1773d7f3a17..c82a921fe17 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -63,6 +63,8 @@ impl PaymentAttemptExt for PaymentAttempt { capture_sequence, connector_capture_id: None, connector_response_reference_id: None, + processor_capture_data: None, + // Below fields are deprecated. Please add any new fields above this line. connector_capture_data: None, }) } diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index c5652e6cbfa..e189deb9487 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -277,7 +277,7 @@ pub async fn generate_sample_data( psd2_sca_exemption_type: None, platform_merchant_id: None, }; - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = ConnectorTransactionId::form_id_and_data(attempt_id.clone()); let payment_attempt = PaymentAttemptBatchNew { attempt_id: attempt_id.clone(), @@ -359,14 +359,14 @@ pub async fn generate_sample_data( organization_id: org_id.clone(), shipping_cost: None, order_tax_amount: None, - connector_transaction_data, + processor_transaction_data, connector_mandate_detail: None, card_discovery: None, }; let refund = if refunds_count < number_of_refunds && !is_failed_payment { refunds_count += 1; - let (connector_transaction_id, connector_transaction_data) = + let (connector_transaction_id, processor_transaction_data) = ConnectorTransactionId::form_id_and_data(attempt_id.clone()); Some(RefundNew { refund_id: common_utils::generate_id_with_default_len("test"), @@ -401,8 +401,8 @@ pub async fn generate_sample_data( charges: None, split_refunds: None, organization_id: org_id.clone(), - connector_refund_data: None, - connector_transaction_data, + processor_refund_data: None, + processor_transaction_data, }) } else { None diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 0c8b969e166..5b5f50f5aa4 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -1490,7 +1490,7 @@ impl DataModelExt for PaymentAttempt { type StorageModel = DieselPaymentAttempt; fn to_storage_model(self) -> Self::StorageModel { - let (connector_transaction_id, connector_transaction_data) = self + let (connector_transaction_id, processor_transaction_data) = self .connector_transaction_id .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) @@ -1565,12 +1565,14 @@ impl DataModelExt for PaymentAttempt { customer_acceptance: self.customer_acceptance, organization_id: self.organization_id, profile_id: self.profile_id, - connector_transaction_data, shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, + // Below fields are deprecated. Please add any new fields above this line. + connector_transaction_data: None, } } diff --git a/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js b/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js index 06c5c799156..7b783847682 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js +++ b/cypress-tests/cypress/e2e/configs/Payment/WorldPay.js @@ -695,4 +695,804 @@ export const connectorDetails = { }, }, }, + pm_list: { + PmListResponse: { + pmListDynamicFieldWithoutBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [{ eligible_connectors: ["worldpay"] }], + required_fields: { + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: null, + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: [ + "AF", + "AU", + "AW", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BZ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BR", + "BN", + "BG", + "BI", + "KH", + "CA", + "CV", + "KY", + "CL", + "CO", + "KM", + "CD", + "CR", + "CZ", + "DZ", + "DK", + "DJ", + "ST", + "DO", + "EC", + "EG", + "SV", + "ER", + "ET", + "FK", + "FJ", + "GM", + "GE", + "GH", + "GI", + "GT", + "GN", + "GY", + "HT", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IL", + "IT", + "JM", + "JP", + "JO", + "KZ", + "KE", + "KW", + "LA", + "LB", + "LS", + "LR", + "LY", + "LT", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "MR", + "MU", + "MX", + "MD", + "MN", + "MA", + "MZ", + "MM", + "NA", + "NZ", + "NI", + "NG", + "KP", + "NO", + "AR", + "PK", + "PG", + "PY", + "PE", + "UY", + "PH", + "PL", + "GB", + "QA", + "OM", + "RO", + "RU", + "RW", + "WS", + "SG", + "ST", + "ZA", + "KR", + "LK", + "SH", + "SD", + "SR", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TT", + "TN", + "TR", + "UG", + "UA", + "US", + "UZ", + "VU", + "VE", + "VN", + "ZM", + "ZW", + ], + }, + }, + value: null, + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: null, + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithBilling: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [{ eligible_connectors: ["worldpay"] }], + required_fields: { + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: "1467", + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: [ + "AF", + "AU", + "AW", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BZ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BR", + "BN", + "BG", + "BI", + "KH", + "CA", + "CV", + "KY", + "CL", + "CO", + "KM", + "CD", + "CR", + "CZ", + "DZ", + "DK", + "DJ", + "ST", + "DO", + "EC", + "EG", + "SV", + "ER", + "ET", + "FK", + "FJ", + "GM", + "GE", + "GH", + "GI", + "GT", + "GN", + "GY", + "HT", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IL", + "IT", + "JM", + "JP", + "JO", + "KZ", + "KE", + "KW", + "LA", + "LB", + "LS", + "LR", + "LY", + "LT", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "MR", + "MU", + "MX", + "MD", + "MN", + "MA", + "MZ", + "MM", + "NA", + "NZ", + "NI", + "NG", + "KP", + "NO", + "AR", + "PK", + "PG", + "PY", + "PE", + "UY", + "PH", + "PL", + "GB", + "QA", + "OM", + "RO", + "RU", + "RW", + "WS", + "SG", + "ST", + "ZA", + "KR", + "LK", + "SH", + "SD", + "SR", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TT", + "TN", + "TR", + "UG", + "UA", + "US", + "UZ", + "VU", + "VE", + "VN", + "ZM", + "ZW", + ], + }, + }, + value: "PL", + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: "San Fransico", + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: "94122", + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithNames: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["worldpay"], + }, + ], + required_fields: { + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: [ + "AF", + "AU", + "AW", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BZ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BR", + "BN", + "BG", + "BI", + "KH", + "CA", + "CV", + "KY", + "CL", + "CO", + "KM", + "CD", + "CR", + "CZ", + "DZ", + "DK", + "DJ", + "ST", + "DO", + "EC", + "EG", + "SV", + "ER", + "ET", + "FK", + "FJ", + "GM", + "GE", + "GH", + "GI", + "GT", + "GN", + "GY", + "HT", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IL", + "IT", + "JM", + "JP", + "JO", + "KZ", + "KE", + "KW", + "LA", + "LB", + "LS", + "LR", + "LY", + "LT", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "MR", + "MU", + "MX", + "MD", + "MN", + "MA", + "MZ", + "MM", + "NA", + "NZ", + "NI", + "NG", + "KP", + "NO", + "AR", + "PK", + "PG", + "PY", + "PE", + "UY", + "PH", + "PL", + "GB", + "QA", + "OM", + "RO", + "RU", + "RW", + "WS", + "SG", + "ST", + "ZA", + "KR", + "LK", + "SH", + "SD", + "SR", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TT", + "TN", + "TR", + "UG", + "UA", + "US", + "UZ", + "VU", + "VE", + "VN", + "ZM", + "ZW", + ], + }, + }, + value: null, + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: null, + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: null, + }, + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + }, + }, + ], + }, + ], + }, + pmListDynamicFieldWithEmail: { + payment_methods: [ + { + payment_method: "card", + payment_method_types: [ + { + payment_method_type: "credit", + card_networks: [ + { + eligible_connectors: ["worldpay"], + }, + ], + required_fields: { + "payment_method_data.card.card_exp_year": { + required_field: "payment_method_data.card.card_exp_year", + display_name: "card_exp_year", + field_type: "user_card_expiry_year", + value: null, + }, + "payment_method_data.card.card_number": { + required_field: "payment_method_data.card.card_number", + display_name: "card_number", + field_type: "user_card_number", + value: null, + }, + "payment_method_data.card.card_exp_month": { + required_field: "payment_method_data.card.card_exp_month", + display_name: "card_exp_month", + field_type: "user_card_expiry_month", + value: null, + }, + "billing.address.zip": { + required_field: "payment_method_data.billing.address.zip", + display_name: "zip", + field_type: "user_address_pincode", + value: null, + }, + "billing.address.city": { + required_field: "payment_method_data.billing.address.city", + display_name: "city", + field_type: "user_address_city", + value: null, + }, + "billing.address.line1": { + required_field: "payment_method_data.billing.address.line1", + display_name: "line1", + field_type: "user_address_line1", + value: null, + }, + "billing.address.country": { + required_field: + "payment_method_data.billing.address.country", + display_name: "country", + field_type: { + user_address_country: { + options: [ + "AF", + "AU", + "AW", + "AZ", + "BS", + "BH", + "BD", + "BB", + "BZ", + "BM", + "BT", + "BO", + "BA", + "BW", + "BR", + "BN", + "BG", + "BI", + "KH", + "CA", + "CV", + "KY", + "CL", + "CO", + "KM", + "CD", + "CR", + "CZ", + "DZ", + "DK", + "DJ", + "ST", + "DO", + "EC", + "EG", + "SV", + "ER", + "ET", + "FK", + "FJ", + "GM", + "GE", + "GH", + "GI", + "GT", + "GN", + "GY", + "HT", + "HN", + "HK", + "HU", + "IS", + "IN", + "ID", + "IR", + "IQ", + "IE", + "IL", + "IT", + "JM", + "JP", + "JO", + "KZ", + "KE", + "KW", + "LA", + "LB", + "LS", + "LR", + "LY", + "LT", + "MO", + "MK", + "MG", + "MW", + "MY", + "MV", + "MR", + "MU", + "MX", + "MD", + "MN", + "MA", + "MZ", + "MM", + "NA", + "NZ", + "NI", + "NG", + "KP", + "NO", + "AR", + "PK", + "PG", + "PY", + "PE", + "UY", + "PH", + "PL", + "GB", + "QA", + "OM", + "RO", + "RU", + "RW", + "WS", + "SG", + "ST", + "ZA", + "KR", + "LK", + "SH", + "SD", + "SR", + "SZ", + "SE", + "CH", + "SY", + "TW", + "TJ", + "TZ", + "TH", + "TT", + "TN", + "TR", + "UG", + "UA", + "US", + "UZ", + "VU", + "VE", + "VN", + "ZM", + "ZW", + ], + }, + }, + value: null, + }, + }, + }, + ], + }, + ], + }, + }, + }, }; diff --git a/migrations/2025-01-09-135057_add_processor_transaction_data/down.sql b/migrations/2025-01-09-135057_add_processor_transaction_data/down.sql new file mode 100644 index 00000000000..bbba634e6e2 --- /dev/null +++ b/migrations/2025-01-09-135057_add_processor_transaction_data/down.sql @@ -0,0 +1,8 @@ +ALTER TABLE payment_attempt +DROP COLUMN IF EXISTS processor_transaction_data; + +ALTER TABLE refund DROP COLUMN IF EXISTS processor_refund_data; + +ALTER TABLE refund DROP COLUMN IF EXISTS processor_transaction_data; + +ALTER TABLE captures DROP COLUMN IF EXISTS processor_capture_data; \ No newline at end of file diff --git a/migrations/2025-01-09-135057_add_processor_transaction_data/up.sql b/migrations/2025-01-09-135057_add_processor_transaction_data/up.sql new file mode 100644 index 00000000000..81e9cdcd074 --- /dev/null +++ b/migrations/2025-01-09-135057_add_processor_transaction_data/up.sql @@ -0,0 +1,11 @@ +ALTER TABLE payment_attempt +ADD COLUMN IF NOT EXISTS processor_transaction_data TEXT; + +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS processor_refund_data TEXT; + +ALTER TABLE refund +ADD COLUMN IF NOT EXISTS processor_transaction_data TEXT; + +ALTER TABLE captures +ADD COLUMN IF NOT EXISTS processor_capture_data TEXT; \ No newline at end of file diff --git a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql index 3926a02afa6..03059985875 100644 --- a/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql +++ b/v2_migrations/2024-08-28-081721_add_v2_columns/up.sql @@ -51,7 +51,7 @@ ADD COLUMN payment_method_type_v2 VARCHAR, ADD COLUMN tax_on_surcharge BIGINT, ADD COLUMN payment_method_billing_address BYTEA, ADD COLUMN redirection_data JSONB, - ADD COLUMN connector_payment_data VARCHAR(512), + ADD COLUMN connector_payment_data TEXT, ADD COLUMN connector_token_details JSONB; -- Change the type of the column from JSON to JSONB diff --git a/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql b/v2_migrations/2025-01-13-081847_drop_v1_columns/down.sql similarity index 98% rename from v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql rename to v2_migrations/2025-01-13-081847_drop_v1_columns/down.sql index f67b9b2e7ac..8d0fedce089 100644 --- a/v2_migrations/2024-11-08-081847_drop_v1_columns/down.sql +++ b/v2_migrations/2025-01-13-081847_drop_v1_columns/down.sql @@ -76,7 +76,7 @@ ADD COLUMN IF NOT EXISTS attempt_id VARCHAR(64) NOT NULL, ADD COLUMN offer_amount bigint, ADD COLUMN payment_method VARCHAR, ADD COLUMN connector_transaction_id VARCHAR(64), - ADD COLUMN connector_transaction_data VARCHAR(512), + ADD COLUMN connector_transaction_data TEXT, ADD COLUMN capture_method "CaptureMethod", ADD COLUMN capture_on TIMESTAMP, ADD COLUMN mandate_id VARCHAR(64), diff --git a/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql b/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql similarity index 92% rename from v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql rename to v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql index e19f8631f62..caac00abd0f 100644 --- a/v2_migrations/2024-11-08-081847_drop_v1_columns/up.sql +++ b/v2_migrations/2025-01-13-081847_drop_v1_columns/up.sql @@ -75,6 +75,7 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN payment_method, DROP COLUMN connector_transaction_id, DROP COLUMN connector_transaction_data, + DROP COLUMN processor_transaction_data, DROP COLUMN capture_method, DROP COLUMN capture_on, DROP COLUMN mandate_id, @@ -89,3 +90,10 @@ ALTER TABLE payment_attempt DROP COLUMN attempt_id, DROP COLUMN payment_method_billing_address_id, DROP COLUMN connector_mandate_detail, DROP COLUMN charge_id; + +-- Run below queries only when V1 is deprecated +ALTER TABLE refund DROP COLUMN connector_refund_data, + DROP COLUMN connector_transaction_data; + +-- Run below queries only when V1 is deprecated +ALTER TABLE captures DROP COLUMN connector_capture_data; From 52ae92bc5df3612d4a15f23c00883db7a5d8d44d Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Wed, 12 Feb 2025 18:11:29 +0530 Subject: [PATCH 087/133] feat(core): 3ds decision manager for v2 (#7089) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 17 ++- crates/api_models/src/payments.rs | 13 ++- crates/diesel_models/src/payment_attempt.rs | 2 +- .../hyperswitch_domain_models/src/payments.rs | 4 +- .../src/payments/payment_attempt.rs | 11 +- .../src/payments/payment_intent.rs | 6 +- crates/router/src/core/admin.rs | 2 +- crates/router/src/core/payments.rs | 32 ++++- .../src/core/payments/conditional_configs.rs | 20 ++++ crates/router/src/core/payments/operations.rs | 11 ++ .../operations/payment_confirm_intent.rs | 32 ++++- .../operations/payment_update_intent.rs | 4 +- crates/router/src/core/payments/routing.rs | 109 +++++++++++++++++- .../router/src/core/payments/transformers.rs | 7 +- 14 files changed, 244 insertions(+), 26 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index ba795d40594..a5dc3a83f2d 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -15042,7 +15042,8 @@ "created", "payment_method_type", "payment_method_subtype", - "merchant_connector_id" + "merchant_connector_id", + "applied_authentication_type" ], "properties": { "id": { @@ -15141,6 +15142,17 @@ } ], "nullable": true + }, + "authentication_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationType" + } + ], + "nullable": true + }, + "applied_authentication_type": { + "$ref": "#/components/schemas/AuthenticationType" } } }, @@ -15499,7 +15511,6 @@ "client_secret", "profile_id", "capture_method", - "authentication_type", "customer_id", "customer_present", "setup_future_usage", @@ -15551,7 +15562,7 @@ "$ref": "#/components/schemas/AuthenticationType" } ], - "default": "no_three_ds" + "nullable": true }, "billing": { "allOf": [ diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 820992025c1..cc70520751a 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -438,8 +438,9 @@ pub struct PaymentsIntentResponse { #[schema(value_type = CaptureMethod, example = "automatic")] pub capture_method: api_enums::CaptureMethod, - #[schema(value_type = AuthenticationType, example = "no_three_ds", default = "no_three_ds")] - pub authentication_type: api_enums::AuthenticationType, + /// The authentication type for the payment + #[schema(value_type = Option, example = "no_three_ds")] + pub authentication_type: Option, /// The billing details of the payment. This address will be used for invoicing. #[schema(value_type = Option
)] @@ -5270,6 +5271,14 @@ pub struct PaymentsConfirmIntentResponse { /// Error details for the payment if any pub error: Option, + + /// The transaction authentication can be set to undergo payer authentication. By default, the authentication will be marked as NO_THREE_DS + #[schema(value_type = Option, example = "no_three_ds")] + pub authentication_type: Option, + + /// The authentication type applied for the payment + #[schema(value_type = AuthenticationType, example = "no_three_ds")] + pub applied_authentication_type: api_enums::AuthenticationType, } /// Token information that can be used to initiate transactions by the merchant. diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index b3b3f44f3c9..2391422ee0c 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -782,7 +782,7 @@ pub enum PaymentAttemptUpdate { #[diesel(table_name = payment_attempt)] pub struct PaymentAttemptUpdateInternal { pub status: Option, - // authentication_type: Option, + pub authentication_type: Option, pub error_message: Option, pub connector_payment_id: Option, // payment_method_id: Option, diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 939d9fee0e1..2457448f213 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -351,7 +351,7 @@ pub struct PaymentIntent { /// Capture method for the payment pub capture_method: storage_enums::CaptureMethod, /// Authentication type that is requested by the merchant for this payment. - pub authentication_type: common_enums::AuthenticationType, + pub authentication_type: Option, /// This contains the pre routing results that are done when routing is done during listing the payment methods. pub prerouting_algorithm: Option, /// The organization id for the payment. This is derived from the merchant account @@ -498,7 +498,7 @@ impl PaymentIntent { .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode shipping address")?, capture_method: request.capture_method.unwrap_or_default(), - authentication_type: request.authentication_type.unwrap_or_default(), + authentication_type: request.authentication_type, prerouting_algorithm: None, organization_id: merchant_account.organization_id.clone(), enable_payment_link: request.payment_link_enabled.unwrap_or_default(), diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index 133d04ceb69..ee8b24c27b3 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -489,6 +489,7 @@ impl PaymentAttempt { .transpose() .change_context(errors::api_error_response::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode billing address")?; + let authentication_type = payment_intent.authentication_type.unwrap_or_default(); Ok(Self { payment_id: payment_intent.id.clone(), @@ -498,7 +499,7 @@ impl PaymentAttempt { // This will be decided by the routing algorithm and updated in update trackers // right before calling the connector connector: None, - authentication_type: payment_intent.authentication_type, + authentication_type, created_at: now, modified_at: now, last_synced: None, @@ -1453,6 +1454,7 @@ pub enum PaymentAttemptUpdate { updated_by: String, connector: String, merchant_connector_id: id_type::MerchantConnectorAccountId, + authentication_type: storage_enums::AuthenticationType, }, /// Update the payment attempt on confirming the intent, after calling the connector on success response ConfirmIntentResponse(Box), @@ -2108,6 +2110,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal updated_by, connector, merchant_connector_id, + authentication_type, } => Self { status: Some(status), error_message: None, @@ -2126,6 +2129,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_capturable: None, amount_to_capture: None, connector_token_details: None, + authentication_type: Some(authentication_type), }, PaymentAttemptUpdate::ErrorUpdate { status, @@ -2151,6 +2155,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_capturable, amount_to_capture: None, connector_token_details: None, + authentication_type: None, }, PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => { let ConfirmIntentResponseUpdate { @@ -2181,6 +2186,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata, amount_to_capture: None, connector_token_details, + authentication_type: None, } } PaymentAttemptUpdate::SyncUpdate { @@ -2205,6 +2211,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, amount_to_capture: None, connector_token_details: None, + authentication_type: None, }, PaymentAttemptUpdate::CaptureUpdate { status, @@ -2228,6 +2235,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal redirection_data: None, connector_metadata: None, connector_token_details: None, + authentication_type: None, }, PaymentAttemptUpdate::PreCaptureUpdate { amount_to_capture, @@ -2250,6 +2258,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, amount_capturable: None, connector_token_details: None, + authentication_type: None, }, } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 2406e5f4b1b..cd16fbcb253 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1387,7 +1387,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_address: shipping_address.map(Encryption::from), capture_method: Some(capture_method), id, - authentication_type: Some(authentication_type), + authentication_type, prerouting_algorithm, merchant_reference_id, surcharge_amount: amount_details.surcharge_amount, @@ -1521,7 +1521,7 @@ impl behaviour::Conversion for PaymentIntent { id: storage_model.id, merchant_reference_id: storage_model.merchant_reference_id, organization_id: storage_model.organization_id, - authentication_type: storage_model.authentication_type.unwrap_or_default(), + authentication_type: storage_model.authentication_type, prerouting_algorithm: storage_model.prerouting_algorithm, enable_payment_link: storage_model.enable_payment_link.into(), apply_mit_exemption: storage_model.apply_mit_exemption.into(), @@ -1592,7 +1592,7 @@ impl behaviour::Conversion for PaymentIntent { capture_method: Some(self.capture_method), id: self.id, merchant_reference_id: self.merchant_reference_id, - authentication_type: Some(self.authentication_type), + authentication_type: self.authentication_type, prerouting_algorithm: self.prerouting_algorithm, surcharge_amount: amount_details.surcharge_amount, tax_on_surcharge: amount_details.tax_on_surcharge, diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index b3f1eab9d9b..0e915dd7708 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -2025,7 +2025,7 @@ impl DefaultFallbackRoutingConfigUpdate<'_> { }; if default_routing_config_for_profile.contains(&choice.clone()) { default_routing_config_for_profile.retain(|mca| { - (mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id)) + mca.merchant_connector_id.as_ref() != Some(self.merchant_connector_id) }); profile_wrapper diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 1b4d00882aa..c81a40216c6 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -176,6 +176,13 @@ where .to_not_found_response(errors::ApiErrorResponse::CustomerNotFound) .attach_printable("Failed while fetching/creating customer")?; + operation + .to_domain()? + .run_decision_manager(state, &mut payment_data, &profile) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to run decision manager")?; + let connector = operation .to_domain()? .perform_routing( @@ -1152,17 +1159,30 @@ where // TODO: Move to business profile surcharge column #[instrument(skip_all)] #[cfg(feature = "v2")] -pub async fn call_decision_manager( +pub fn call_decision_manager( state: &SessionState, - merchant_account: &domain::MerchantAccount, - _business_profile: &domain::Profile, - payment_data: &D, + record: common_types::payments::DecisionManagerRecord, + payment_data: &PaymentConfirmData, ) -> RouterResult> where F: Clone, - D: OperationSessionGetters, { - todo!() + let payment_method_data = payment_data.get_payment_method_data(); + let payment_dsl_data = core_routing::PaymentsDslInput::new( + None, + payment_data.get_payment_attempt(), + payment_data.get_payment_intent(), + payment_method_data, + payment_data.get_address(), + None, + payment_data.get_currency(), + ); + + let output = perform_decision_management(record, &payment_dsl_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Could not decode the conditional config")?; + + Ok(output.override_3ds) } #[cfg(feature = "v2")] diff --git a/crates/router/src/core/payments/conditional_configs.rs b/crates/router/src/core/payments/conditional_configs.rs index d511c0fd6a7..55e5c657b13 100644 --- a/crates/router/src/core/payments/conditional_configs.rs +++ b/crates/router/src/core/payments/conditional_configs.rs @@ -6,6 +6,8 @@ use router_env::{instrument, tracing}; use storage_impl::redis::cache::{self, DECISION_MANAGER_CACHE}; use super::routing::make_dsl_input; +#[cfg(feature = "v2")] +use crate::{core::errors::RouterResult, types::domain}; use crate::{ core::{errors, errors::ConditionalConfigError as ConfigError, routing as core_routing}, routes, @@ -13,6 +15,7 @@ use crate::{ pub type ConditionalConfigResult = errors::CustomResult; #[instrument(skip_all)] +#[cfg(feature = "v1")] pub async fn perform_decision_management( state: &routes::SessionState, algorithm_ref: routing::RoutingAlgorithmRef, @@ -57,6 +60,23 @@ pub async fn perform_decision_management( execute_dsl_and_get_conditional_config(backend_input, &interpreter) } +#[cfg(feature = "v2")] +pub fn perform_decision_management( + record: common_types::payments::DecisionManagerRecord, + payment_data: &core_routing::PaymentsDslInput<'_>, +) -> RouterResult { + let interpreter = backend::VirInterpreterBackend::with_program(record.program) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error initializing DSL interpreter backend")?; + + let backend_input = make_dsl_input(payment_data) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error constructing DSL input")?; + execute_dsl_and_get_conditional_config(backend_input, &interpreter) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error executing DSL") +} + pub fn execute_dsl_and_get_conditional_config( backend_input: dsl_inputs::BackendInput, interpreter: &backend::VirInterpreterBackend, diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index 911530ba8eb..e345abbf9c4 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -231,6 +231,17 @@ pub trait Domain: Send + Sync { storage_scheme: enums::MerchantStorageScheme, ) -> CustomResult<(BoxedOperation<'a, F, R, D>, Option), errors::StorageError>; + #[cfg(feature = "v2")] + /// This will run the decision manager for the payment + async fn run_decision_manager<'a>( + &'a self, + state: &SessionState, + payment_data: &mut D, + business_profile: &domain::Profile, + ) -> CustomResult<(), errors::ApiErrorResponse> { + Ok(()) + } + #[allow(clippy::too_many_arguments)] async fn make_pm_data<'a>( &'a self, diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 71d99d03e41..043ee3378b3 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -17,9 +17,10 @@ use crate::{ admin, errors::{self, CustomResult, RouterResult, StorageErrorExt}, payments::{ - self, helpers, + self, call_decision_manager, helpers, operations::{self, ValidateStatusForOperation}, - populate_surcharge_details, CustomerDetails, PaymentAddress, PaymentData, + populate_surcharge_details, CustomerDetails, OperationSessionSetters, PaymentAddress, + PaymentData, }, utils as core_utils, }, @@ -290,6 +291,30 @@ impl Domain( + &'a self, + state: &SessionState, + payment_data: &mut PaymentConfirmData, + business_profile: &domain::Profile, + ) -> CustomResult<(), errors::ApiErrorResponse> { + let authentication_type = payment_data.payment_intent.authentication_type; + + let authentication_type = match business_profile.three_ds_decision_manager_config.as_ref() { + Some(three_ds_decision_manager_config) => call_decision_manager( + state, + three_ds_decision_manager_config.clone(), + payment_data, + )?, + None => authentication_type, + }; + + if let Some(auth_type) = authentication_type { + payment_data.payment_attempt.authentication_type = auth_type; + } + + Ok(()) + } + #[instrument(skip_all)] async fn make_pm_data<'a>( &'a self, @@ -397,11 +422,14 @@ impl UpdateTracker, PaymentsConfirmInt active_attempt_id: payment_data.payment_attempt.id.clone(), }; + let authentication_type = payment_data.payment_attempt.authentication_type; + let payment_attempt_update = hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptUpdate::ConfirmIntent { status: attempt_status, updated_by: storage_scheme.to_string(), connector, merchant_connector_id, + authentication_type, }; let updated_payment_intent = db diff --git a/crates/router/src/core/payments/operations/payment_update_intent.rs b/crates/router/src/core/payments/operations/payment_update_intent.rs index 4782d237e21..2931c11b070 100644 --- a/crates/router/src/core/payments/operations/payment_update_intent.rs +++ b/crates/router/src/core/payments/operations/payment_update_intent.rs @@ -259,7 +259,7 @@ impl GetTracker, PaymentsUpda .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable("Unable to decode shipping address")?, capture_method: capture_method.unwrap_or(payment_intent.capture_method), - authentication_type: authentication_type.unwrap_or(payment_intent.authentication_type), + authentication_type: authentication_type.or(payment_intent.authentication_type), payment_link_config: payment_link_config .map(ApiModelToDieselModelConvertor::convert_from) .or(payment_intent.payment_link_config), @@ -324,7 +324,7 @@ impl UpdateTracker, PaymentsUpdateIn tax_on_surcharge: intent.amount_details.tax_on_surcharge, routing_algorithm_id: intent.routing_algorithm_id, capture_method: Some(intent.capture_method), - authentication_type: Some(intent.authentication_type), + authentication_type: intent.authentication_type, billing_address: intent.billing_address, shipping_address: intent.shipping_address, customer_present: Some(intent.customer_present), diff --git a/crates/router/src/core/payments/routing.rs b/crates/router/src/core/payments/routing.rs index 41be7e6e2d3..cf5cd35fb98 100644 --- a/crates/router/src/core/payments/routing.rs +++ b/crates/router/src/core/payments/routing.rs @@ -173,7 +173,112 @@ pub fn make_dsl_input_for_payouts( pub fn make_dsl_input( payments_dsl_input: &routing::PaymentsDslInput<'_>, ) -> RoutingResult { - todo!() + let mandate_data = dsl_inputs::MandateData { + mandate_acceptance_type: payments_dsl_input.setup_mandate.as_ref().and_then( + |mandate_data| { + mandate_data + .customer_acceptance + .as_ref() + .map(|customer_accept| match customer_accept.acceptance_type { + hyperswitch_domain_models::mandates::AcceptanceType::Online => { + euclid_enums::MandateAcceptanceType::Online + } + hyperswitch_domain_models::mandates::AcceptanceType::Offline => { + euclid_enums::MandateAcceptanceType::Offline + } + }) + }, + ), + mandate_type: payments_dsl_input + .setup_mandate + .as_ref() + .and_then(|mandate_data| { + mandate_data + .mandate_type + .clone() + .map(|mandate_type| match mandate_type { + hyperswitch_domain_models::mandates::MandateDataType::SingleUse(_) => { + euclid_enums::MandateType::SingleUse + } + hyperswitch_domain_models::mandates::MandateDataType::MultiUse(_) => { + euclid_enums::MandateType::MultiUse + } + }) + }), + payment_type: Some( + if payments_dsl_input + .recurring_details + .as_ref() + .is_some_and(|data| { + matches!( + data, + api_models::mandates::RecurringDetails::ProcessorPaymentToken(_) + ) + }) + { + euclid_enums::PaymentType::PptMandate + } else { + payments_dsl_input.setup_mandate.map_or_else( + || euclid_enums::PaymentType::NonMandate, + |_| euclid_enums::PaymentType::SetupMandate, + ) + }, + ), + }; + let payment_method_input = dsl_inputs::PaymentMethodInput { + payment_method: Some(payments_dsl_input.payment_attempt.payment_method_type), + payment_method_type: Some(payments_dsl_input.payment_attempt.payment_method_subtype), + card_network: payments_dsl_input + .payment_method_data + .as_ref() + .and_then(|pm_data| match pm_data { + domain::PaymentMethodData::Card(card) => card.card_network.clone(), + + _ => None, + }), + }; + + let payment_input = dsl_inputs::PaymentInput { + amount: payments_dsl_input + .payment_attempt + .amount_details + .get_net_amount(), + card_bin: payments_dsl_input.payment_method_data.as_ref().and_then( + |pm_data| match pm_data { + domain::PaymentMethodData::Card(card) => Some(card.card_number.get_card_isin()), + _ => None, + }, + ), + currency: payments_dsl_input.currency, + authentication_type: Some(payments_dsl_input.payment_attempt.authentication_type), + capture_method: Some(payments_dsl_input.payment_intent.capture_method), + business_country: None, + billing_country: payments_dsl_input + .address + .get_payment_method_billing() + .and_then(|billing_address| billing_address.address.as_ref()) + .and_then(|address_details| address_details.country) + .map(api_enums::Country::from_alpha2), + business_label: None, + setup_future_usage: Some(payments_dsl_input.payment_intent.setup_future_usage), + }; + + let metadata = payments_dsl_input + .payment_intent + .metadata + .clone() + .map(|value| value.parse_value("routing_parameters")) + .transpose() + .change_context(errors::RoutingError::MetadataParsingError) + .attach_printable("Unable to parse routing_parameters from metadata of payment_intent") + .unwrap_or(None); + + Ok(dsl_inputs::BackendInput { + metadata, + payment: payment_input, + payment_method: payment_method_input, + mandate: mandate_data, + }) } #[cfg(feature = "v1")] @@ -185,7 +290,7 @@ pub fn make_dsl_input( |mandate_data| { mandate_data .customer_acceptance - .clone() + .as_ref() .map(|cat| match cat.acceptance_type { hyperswitch_domain_models::mandates::AcceptanceType::Online => { euclid_enums::MandateAcceptanceType::Online diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index ebf837792dc..3075c9f26d7 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -787,7 +787,10 @@ pub async fn construct_payment_router_data_for_sdk_session<'a>( .map(ToOwned::to_owned), // TODO: Create unified address address: hyperswitch_domain_models::payment_address::PaymentAddress::default(), - auth_type: payment_data.payment_intent.authentication_type, + auth_type: payment_data + .payment_intent + .authentication_type + .unwrap_or_default(), connector_meta_data: merchant_connector_account.get_metadata(), connector_wallets_details: None, request, @@ -1647,6 +1650,8 @@ where merchant_connector_id, browser_info: None, error, + authentication_type: payment_intent.authentication_type, + applied_authentication_type: payment_attempt.authentication_type, }; Ok(services::ApplicationResponse::JsonWithHeaders(( From 8bb94afdc4bad69e726fb5acb9d12599c66d98ff Mon Sep 17 00:00:00 2001 From: likhinbopanna <131246334+likhinbopanna@users.noreply.github.com> Date: Wed, 12 Feb 2025 23:30:33 +0530 Subject: [PATCH 088/133] ci(cypress): add test cases for duplicate requests (#7220) --- .../cypress/e2e/configs/Payment/Commons.js | 38 ++++++ .../e2e/spec/Payment/00022-Variations.cy.js | 113 ++++++++++++++++++ cypress-tests/cypress/support/commands.js | 49 +++++--- 3 files changed, 184 insertions(+), 16 deletions(-) diff --git a/cypress-tests/cypress/e2e/configs/Payment/Commons.js b/cypress-tests/cypress/e2e/configs/Payment/Commons.js index 0f551c97c63..6cf29db5f1f 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Commons.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Commons.js @@ -1470,6 +1470,44 @@ export const connectorDetails = { }, }, }, + DuplicatePaymentID: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: + "The payment with the specified payment_id already exists in our records", + code: "HE_01", + }, + }, + }, + }, + DuplicateRefundID: { + Request: { + amount: 2000, + }, + Response: { + status: 400, + body: { + error: { + type: "invalid_request", + message: + "Duplicate refund request. Refund already attempted with the refund ID", + code: "HE_01", + }, + }, + }, + }, }, upi_pm: { PaymentIntent: getCustomExchange({ diff --git a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js index f3a99df1ba2..8180f30bd4a 100644 --- a/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js +++ b/cypress-tests/cypress/e2e/spec/Payment/00022-Variations.cy.js @@ -702,4 +702,117 @@ describe("Corner cases", () => { if (shouldContinue) shouldContinue = utils.should_continue_further(data); }); }); + + context("Duplicate Payment ID", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create new payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Retrieve payment", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["No3DSAutoCapture"]; + + cy.retrievePaymentCallTest(globalState, data); + }); + + it("Create a payment with a duplicate payment ID", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["DuplicatePaymentID"]; + + data.Request.payment_id = globalState.get("paymentID"); + + cy.createConfirmPaymentTest( + fixtures.createConfirmPaymentBody, + data, + "no_three_ds", + "automatic", + globalState + ); + + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Duplicate Refund ID", () => { + let shouldContinue = true; // variable that will be used to skip tests if a previous test fails + + beforeEach(function () { + if (!shouldContinue) { + this.skip(); + } + }); + + it("Create new refund", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["PartialRefund"]; + + cy.refundCallTest(fixtures.refundBody, data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Sync refund", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["SyncRefund"]; + + cy.syncRefundCallTest(data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + + it("Create a refund with a duplicate refund ID", () => { + const data = getConnectorDetails(globalState.get("connectorId"))[ + "card_pm" + ]["DuplicateRefundID"]; + + data.Request.refund_id = globalState.get("refundId"); + + cy.refundCallTest(fixtures.refundBody, data, globalState); + if (shouldContinue) shouldContinue = utils.should_continue_further(data); + }); + }); + + context("Duplicate Customer ID", () => { + before("seed global state", () => { + cy.task("getGlobalState").then((state) => { + globalState = new State(state); + }); + }); + + after("flush global state", () => { + cy.task("setGlobalState", globalState.data); + }); + + it("Create new customer", () => { + cy.createCustomerCallTest(fixtures.customerCreateBody, globalState); + }); + + it("Create a customer with a duplicate customer ID", () => { + const customerData = fixtures.customerCreateBody; + customerData.customer_id = globalState.get("customerId"); + + cy.createCustomerCallTest(customerData, globalState); + }); + }); }); diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 77f94cf30fa..fc25220b80b 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -888,25 +888,40 @@ Cypress.Commands.add( "api-key": globalState.get("apiKey"), }, body: customerCreateBody, + failOnStatusCode: false, }).then((response) => { - globalState.set("customerId", response.body.customer_id); logRequestId(response.headers["x-request-id"]); cy.wrap(response).then(() => { - expect(response.body.customer_id, "customer_id").to.not.be.empty; - expect(customerCreateBody.email, "email").to.equal(response.body.email); - expect(customerCreateBody.name, "name").to.equal(response.body.name); - expect(customerCreateBody.phone, "phone").to.equal(response.body.phone); - expect(customerCreateBody.metadata, "metadata").to.deep.equal( - response.body.metadata - ); - expect(customerCreateBody.address, "address").to.deep.equal( - response.body.address - ); - expect( - customerCreateBody.phone_country_code, - "phone_country_code" - ).to.equal(response.body.phone_country_code); + if (response.status === 200) { + globalState.set("customerId", response.body.customer_id); + + expect(response.body.customer_id, "customer_id").to.not.be.empty; + expect(customerCreateBody.email, "email").to.equal( + response.body.email + ); + expect(customerCreateBody.name, "name").to.equal(response.body.name); + expect(customerCreateBody.phone, "phone").to.equal( + response.body.phone + ); + expect(customerCreateBody.metadata, "metadata").to.deep.equal( + response.body.metadata + ); + expect(customerCreateBody.address, "address").to.deep.equal( + response.body.address + ); + expect( + customerCreateBody.phone_country_code, + "phone_country_code" + ).to.equal(response.body.phone_country_code); + } else if (response.status === 400) { + if (response.body.error.message.includes("already exists")) { + expect(response.body.error.code).to.equal("IR_12"); + expect(response.body.error.message).to.equal( + "Customer with the given `customer_id` already exists" + ); + } + } }); }); } @@ -2351,7 +2366,9 @@ Cypress.Commands.add("refundCallTest", (requestBody, data, globalState) => { // we only need this to set the delay. We don't need the return value execConfig(validateConfig(configs)); - requestBody.amount = reqData.amount; + for (const key in reqData) { + requestBody[key] = reqData[key]; + } requestBody.payment_id = payment_id; cy.request({ From 34487333cc722fb8f7d4fa911b54cf730a2b71ab Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 13 Feb 2025 00:27:07 +0000 Subject: [PATCH 089/133] chore(version): 2025.02.13.0 --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b0d36a756..57ff6ff0fd1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,26 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.13.0 + +### Features + +- **core:** 3ds decision manager for v2 ([#7089](https://github.com/juspay/hyperswitch/pull/7089)) ([`52ae92b`](https://github.com/juspay/hyperswitch/commit/52ae92bc5df3612d4a15f23c00883db7a5d8d44d)) + +### Bug Fixes + +- **v2:** Trait gating in v2 ([#7223](https://github.com/juspay/hyperswitch/pull/7223)) ([`fd81197`](https://github.com/juspay/hyperswitch/commit/fd8119782a5d78a4be4561b44d0f68f498fe25b9)) + +### Refactors + +- **connector:** [Adyen] Removed deprecated PMTs from Ayden (Giropay, Sofort) ([#7100](https://github.com/juspay/hyperswitch/pull/7100)) ([`40a36fd`](https://github.com/juspay/hyperswitch/commit/40a36fd319ccdb495deb077005ffcaea9cdf2427)) +- **cypress:** Make amount configurable ([#7219](https://github.com/juspay/hyperswitch/pull/7219)) ([`055f628`](https://github.com/juspay/hyperswitch/commit/055f62858e6d0bcc6d27f563b30804365106d4a6)) +- **schema:** Add a new column for storing large connector transaction IDs ([#7017](https://github.com/juspay/hyperswitch/pull/7017)) ([`fa09db1`](https://github.com/juspay/hyperswitch/commit/fa09db1534884037947c6d488e33a3ce600c2a0c)) + +**Full Changelog:** [`2025.02.12.0...2025.02.13.0`](https://github.com/juspay/hyperswitch/compare/2025.02.12.0...2025.02.13.0) + +- - - + ## 2025.02.12.0 ### Features From 6aac16e0c997d36e653f91be0f2a6660a3378dd5 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 13 Feb 2025 12:32:21 +0530 Subject: [PATCH 090/133] fix(connectors): [fiuu] zero amount mandate flow for wallets (#7251) --- .../src/connectors/novalnet/transformers.rs | 14 ++++++++------ crates/hyperswitch_connectors/src/utils.rs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index ba00162281f..44f84bf475d 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -28,7 +28,7 @@ use strum::Display; use crate::{ types::{RefundsResponseRouterData, ResponseRouterData}, utils::{ - self, AddressDetailsData, ApplePay, PaymentsAuthorizeRequestData, + self, AddressData, AddressDetailsData, ApplePay, PaymentsAuthorizeRequestData, PaymentsCancelRequestData, PaymentsCaptureRequestData, PaymentsSetupMandateRequestData, PaymentsSyncRequestData, RefundsRequestData, RouterData as _, }, @@ -1467,7 +1467,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { enums::AuthenticationType::NoThreeDs => None, }; let test_mode = get_test_mode(item.test_mode); - let req_address = item.get_billing_address()?.to_owned(); + let req_address = item.get_optional_billing(); let billing = NovalnetPaymentsRequestBilling { house_no: item.get_optional_billing_line1(), @@ -1477,10 +1477,12 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { country_code: item.get_optional_billing_country(), }; + let email = item.get_billing_email().or(item.request.get_email())?; + let customer = NovalnetPaymentsRequestCustomer { - first_name: req_address.get_optional_first_name(), - last_name: req_address.get_optional_last_name(), - email: item.request.get_email()?.clone(), + first_name: req_address.and_then(|addr| addr.get_optional_first_name()), + last_name: req_address.and_then(|addr| addr.get_optional_last_name()), + email, mobile: item.get_optional_billing_phone_number(), billing: Some(billing), // no_nc is used to indicate if minimal customer data is passed or not @@ -1504,7 +1506,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { card_expiry_month: req_card.card_exp_month.clone(), card_expiry_year: req_card.card_exp_year.clone(), card_cvc: req_card.card_cvc.clone(), - card_holder: req_address.get_full_name()?.clone(), + card_holder: item.get_billing_address()?.get_full_name()?, }); let transaction = NovalnetPaymentsRequestTransaction { diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index aec4d31072d..af8e89eeced 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1935,6 +1935,8 @@ pub trait AddressData { fn get_optional_full_name(&self) -> Option>; fn get_email(&self) -> Result; fn get_phone_with_country_code(&self) -> Result, Error>; + fn get_optional_first_name(&self) -> Option>; + fn get_optional_last_name(&self) -> Option>; } impl AddressData for Address { @@ -1955,6 +1957,18 @@ impl AddressData for Address { .transpose()? .ok_or_else(missing_field_err("phone")) } + + fn get_optional_first_name(&self) -> Option> { + self.address + .as_ref() + .and_then(|billing_address| billing_address.get_optional_first_name()) + } + + fn get_optional_last_name(&self) -> Option> { + self.address + .as_ref() + .and_then(|billing_address| billing_address.get_optional_last_name()) + } } pub trait PaymentsPreProcessingRequestData { fn get_redirect_response_payload(&self) -> Result; From 66d9c731f528cd33a1a94815485d6efceb493742 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Thu, 13 Feb 2025 13:10:28 +0530 Subject: [PATCH 091/133] feat(core): add support to generate session token response from both `connector_wallets_details` and `metadata` (#7140) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 16 +- api-reference/openapi_spec.json | 16 +- crates/api_models/src/payments.rs | 24 +- .../src/configs/secrets_transformers.rs | 34 +-- crates/router/src/configs/settings.rs | 4 +- .../src/connector/trustpay/transformers.rs | 4 +- crates/router/src/consts.rs | 6 + crates/router/src/core/errors.rs | 2 - crates/router/src/core/payments.rs | 49 ++-- .../src/core/payments/flows/session_flow.rs | 256 +++++++++++++----- crates/router/src/core/payments/helpers.rs | 49 ++-- 11 files changed, 297 insertions(+), 163 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index a5dc3a83f2d..cadae4fa45c 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -9967,13 +9967,11 @@ }, "GpayTokenParameters": { "type": "object", - "required": [ - "gateway" - ], "properties": { "gateway": { "type": "string", - "description": "The name of the connector" + "description": "The name of the connector", + "nullable": true }, "gateway_merchant_id": { "type": "string", @@ -9987,6 +9985,16 @@ "stripe:publishableKey": { "type": "string", "nullable": true + }, + "protocol_version": { + "type": "string", + "description": "The protocol version for encryption", + "nullable": true + }, + "public_key": { + "type": "string", + "description": "The public key provided by the merchant", + "nullable": true } } }, diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 60be7f59d67..61892d1b29e 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -12654,13 +12654,11 @@ }, "GpayTokenParameters": { "type": "object", - "required": [ - "gateway" - ], "properties": { "gateway": { "type": "string", - "description": "The name of the connector" + "description": "The name of the connector", + "nullable": true }, "gateway_merchant_id": { "type": "string", @@ -12674,6 +12672,16 @@ "stripe:publishableKey": { "type": "string", "nullable": true + }, + "protocol_version": { + "type": "string", + "description": "The protocol version for encryption", + "nullable": true + }, + "public_key": { + "type": "string", + "description": "The public key provided by the merchant", + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index cc70520751a..bec25529af9 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6085,7 +6085,8 @@ pub enum GpayBillingAddressFormat { #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] pub struct GpayTokenParameters { /// The name of the connector - pub gateway: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub gateway: Option, /// The merchant ID registered in the connector associated #[serde(skip_serializing_if = "Option::is_none")] pub gateway_merchant_id: Option, @@ -6096,6 +6097,13 @@ pub struct GpayTokenParameters { rename = "stripe:publishableKey" )] pub stripe_publishable_key: Option, + /// The protocol version for encryption + #[serde(skip_serializing_if = "Option::is_none")] + pub protocol_version: Option, + /// The public key provided by the merchant + #[serde(skip_serializing_if = "Option::is_none")] + #[schema(value_type = Option)] + pub public_key: Option>, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] @@ -6365,6 +6373,7 @@ pub struct GooglePayWalletDetails { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GooglePayDetails { pub provider_details: GooglePayProviderDetails, + pub cards: GpayAllowedMethodsParameters, } // Google Pay Provider Details can of two types: GooglePayMerchantDetails or GooglePayHyperSwitchDetails @@ -6383,6 +6392,7 @@ pub struct GooglePayMerchantDetails { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GooglePayMerchantInfo { pub merchant_name: String, + pub merchant_id: Option, pub tokenization_specification: GooglePayTokenizationSpecification, } @@ -6393,8 +6403,9 @@ pub struct GooglePayTokenizationSpecification { pub parameters: GooglePayTokenizationParameters, } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize, strum::Display)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] pub enum GooglePayTokenizationType { PaymentGateway, Direct, @@ -6402,10 +6413,13 @@ pub enum GooglePayTokenizationType { #[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] pub struct GooglePayTokenizationParameters { - pub gateway: String, - pub public_key: Secret, - pub private_key: Secret, + pub gateway: Option, + pub public_key: Option>, + pub private_key: Option>, pub recipient_id: Option>, + pub gateway_merchant_id: Option>, + pub stripe_publishable_key: Option>, + pub stripe_version: Option>, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] diff --git a/crates/router/src/configs/secrets_transformers.rs b/crates/router/src/configs/secrets_transformers.rs index f51a785eaa8..65ac6adf5bc 100644 --- a/crates/router/src/configs/secrets_transformers.rs +++ b/crates/router/src/configs/secrets_transformers.rs @@ -199,24 +199,6 @@ impl SecretsHandler for settings::PazeDecryptConfig { } } -#[async_trait::async_trait] -impl SecretsHandler for settings::GooglePayDecryptConfig { - async fn convert_to_raw_secret( - value: SecretStateContainer, - secret_management_client: &dyn SecretManagementInterface, - ) -> CustomResult, SecretsManagementError> { - let google_pay_decrypt_keys = value.get_inner(); - - let google_pay_root_signing_keys = secret_management_client - .get_secret(google_pay_decrypt_keys.google_pay_root_signing_keys.clone()) - .await?; - - Ok(value.transition_state(|_| Self { - google_pay_root_signing_keys, - })) - } -} - #[async_trait::async_trait] impl SecretsHandler for settings::ApplepayMerchantConfigs { async fn convert_to_raw_secret( @@ -438,20 +420,6 @@ pub(crate) async fn fetch_raw_secrets( None }; - #[allow(clippy::expect_used)] - let google_pay_decrypt_keys = if let Some(google_pay_keys) = conf.google_pay_decrypt_keys { - Some( - settings::GooglePayDecryptConfig::convert_to_raw_secret( - google_pay_keys, - secret_management_client, - ) - .await - .expect("Failed to decrypt google pay decrypt configs"), - ) - } else { - None - }; - #[allow(clippy::expect_used)] let applepay_merchant_configs = settings::ApplepayMerchantConfigs::convert_to_raw_secret( conf.applepay_merchant_configs, @@ -544,7 +512,7 @@ pub(crate) async fn fetch_raw_secrets( payouts: conf.payouts, applepay_decrypt_keys, paze_decrypt_keys, - google_pay_decrypt_keys, + google_pay_decrypt_keys: conf.google_pay_decrypt_keys, multiple_api_version_supported_connectors: conf.multiple_api_version_supported_connectors, applepay_merchant_configs, lock_settings: conf.lock_settings, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index d721a4db967..5445cb8697f 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -99,7 +99,7 @@ pub struct Settings { pub payout_method_filters: ConnectorFilters, pub applepay_decrypt_keys: SecretStateContainer, pub paze_decrypt_keys: Option>, - pub google_pay_decrypt_keys: Option>, + pub google_pay_decrypt_keys: Option, pub multiple_api_version_supported_connectors: MultipleApiVersionSupportedConnectors, pub applepay_merchant_configs: SecretStateContainer, pub lock_settings: LockSettings, @@ -919,7 +919,7 @@ impl Settings { self.google_pay_decrypt_keys .as_ref() - .map(|x| x.get_inner().validate()) + .map(|x| x.validate()) .transpose()?; self.key_manager.get_inner().validate()?; diff --git a/crates/router/src/connector/trustpay/transformers.rs b/crates/router/src/connector/trustpay/transformers.rs index 78641d7e33b..b7cb7a91f20 100644 --- a/crates/router/src/connector/trustpay/transformers.rs +++ b/crates/router/src/connector/trustpay/transformers.rs @@ -1364,10 +1364,12 @@ impl From for api_models::payments::GpayTokenizat impl From for api_models::payments::GpayTokenParameters { fn from(value: GpayTokenParameters) -> Self { Self { - gateway: value.gateway, + gateway: Some(value.gateway), gateway_merchant_id: Some(value.gateway_merchant_id.expose()), stripe_version: None, stripe_publishable_key: None, + public_key: None, + protocol_version: None, } } } diff --git a/crates/router/src/consts.rs b/crates/router/src/consts.rs index 05d08f37ddd..dc3c2d532e8 100644 --- a/crates/router/src/consts.rs +++ b/crates/router/src/consts.rs @@ -220,3 +220,9 @@ pub const DEFAULT_PAYMENT_METHOD_SESSION_EXPIRY: u32 = 15 * 60; // 15 minutes /// Authorize flow identifier used for performing GSM operations pub const AUTHORIZE_FLOW_STR: &str = "Authorize"; + +/// Protocol Version for encrypted Google Pay Token +pub(crate) const PROTOCOL: &str = "ECv2"; + +/// Sender ID for Google Pay Decryption +pub(crate) const SENDER_ID: &[u8] = b"Google"; diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 54cae42ceb3..506ce557192 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -246,8 +246,6 @@ pub enum PazeDecryptionError { #[derive(Debug, thiserror::Error)] pub enum GooglePayDecryptionError { - #[error("Recipient ID not found")] - RecipientIdNotFound, #[error("Invalid expiration time")] InvalidExpirationTime, #[error("Failed to base64 decode input data")] diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index c81a40216c6..fd2950f5069 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4381,22 +4381,13 @@ fn get_google_pay_connector_wallet_details( state: &SessionState, merchant_connector_account: &helpers::MerchantConnectorAccountType, ) -> Option { - let google_pay_root_signing_keys = - state - .conf - .google_pay_decrypt_keys - .as_ref() - .map(|google_pay_keys| { - google_pay_keys - .get_inner() - .google_pay_root_signing_keys - .clone() - }); - match ( - google_pay_root_signing_keys, - merchant_connector_account.get_connector_wallets_details(), - ) { - (Some(google_pay_root_signing_keys), Some(wallet_details)) => { + let google_pay_root_signing_keys = state + .conf + .google_pay_decrypt_keys + .as_ref() + .map(|google_pay_keys| google_pay_keys.google_pay_root_signing_keys.clone()); + match merchant_connector_account.get_connector_wallets_details() { + Some(wallet_details) => { let google_pay_wallet_details = wallet_details .parse_value::( "GooglePayWalletDetails", @@ -4407,31 +4398,43 @@ fn get_google_pay_connector_wallet_details( google_pay_wallet_details .ok() - .map( + .and_then( |google_pay_wallet_details| { match google_pay_wallet_details .google_pay .provider_details { api_models::payments::GooglePayProviderDetails::GooglePayMerchantDetails(merchant_details) => { - GooglePayPaymentProcessingDetails { - google_pay_private_key: merchant_details + match ( + merchant_details .merchant_info .tokenization_specification .parameters .private_key, google_pay_root_signing_keys, - google_pay_recipient_id: merchant_details + merchant_details .merchant_info .tokenization_specification .parameters .recipient_id, - } + ) { + (Some(google_pay_private_key), Some(google_pay_root_signing_keys), Some(google_pay_recipient_id)) => { + Some(GooglePayPaymentProcessingDetails { + google_pay_private_key, + google_pay_root_signing_keys, + google_pay_recipient_id + }) + } + _ => { + logger::warn!("One or more of the following fields are missing in GooglePayMerchantDetails: google_pay_private_key, google_pay_root_signing_keys, google_pay_recipient_id"); + None + } + } } } } ) } - _ => None, + None => None, } } @@ -4557,7 +4560,7 @@ pub struct PazePaymentProcessingDetails { pub struct GooglePayPaymentProcessingDetails { pub google_pay_private_key: Secret, pub google_pay_root_signing_keys: Secret, - pub google_pay_recipient_id: Option>, + pub google_pay_recipient_id: Secret, } #[derive(Clone, Debug)] diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index 65688c5d465..f6e5d1e7f05 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -1,4 +1,4 @@ -use api_models::payments as payment_types; +use api_models::{admin as admin_types, payments as payment_types}; use async_trait::async_trait; use common_utils::{ ext_traits::ByteSliceExt, @@ -8,10 +8,11 @@ use common_utils::{ use error_stack::{Report, ResultExt}; #[cfg(feature = "v2")] use hyperswitch_domain_models::payments::PaymentIntentData; -use masking::ExposeInterface; +use masking::{ExposeInterface, ExposeOptionInterface}; use super::{ConstructFlowSpecificData, Feature}; use crate::{ + consts::PROTOCOL, core::{ errors::{self, ConnectorErrorExt, RouterResult}, payments::{self, access_token, helpers, transformers, PaymentData}, @@ -828,6 +829,21 @@ fn create_gpay_session_token( connector: &api::ConnectorData, business_profile: &domain::Profile, ) -> RouterResult { + // connector_wallet_details is being parse into admin types to check specifically if google_pay field is present + // this is being done because apple_pay details from metadata is also being filled into connector_wallets_details + let connector_wallets_details = router_data + .connector_wallets_details + .clone() + .parse_value::("ConnectorWalletDetails") + .change_context(errors::ConnectorError::NoConnectorWalletDetails) + .attach_printable(format!( + "cannot parse connector_wallets_details from the given value {:?}", + router_data.connector_wallets_details + )) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_wallets_details".to_string(), + expected_format: "admin_types_connector_wallets_details_format".to_string(), + })?; let connector_metadata = router_data.connector_meta_data.clone(); let delayed_response = is_session_response_delayed(state, connector); @@ -849,18 +865,6 @@ fn create_gpay_session_token( ..router_data.clone() }) } else { - let gpay_data = connector_metadata - .clone() - .parse_value::("GpaySessionTokenData") - .change_context(errors::ConnectorError::NoConnectorMetaData) - .attach_printable(format!( - "cannot parse gpay metadata from the given value {connector_metadata:?}" - )) - .change_context(errors::ApiErrorResponse::InvalidDataFormat { - field_name: "connector_metadata".to_string(), - expected_format: "gpay_metadata_format".to_string(), - })?; - let always_collect_billing_details_from_wallet_connector = business_profile .always_collect_billing_details_from_wallet_connector .unwrap_or(false); @@ -883,27 +887,6 @@ fn create_gpay_session_token( false }; - let billing_address_parameters = - is_billing_details_required.then_some(payment_types::GpayBillingAddressParameters { - phone_number_required: is_billing_details_required, - format: payment_types::GpayBillingAddressFormat::FULL, - }); - - let gpay_allowed_payment_methods = gpay_data - .data - .allowed_payment_methods - .into_iter() - .map( - |allowed_payment_methods| payment_types::GpayAllowedPaymentMethods { - parameters: payment_types::GpayAllowedMethodsParameters { - billing_address_required: Some(is_billing_details_required), - billing_address_parameters: billing_address_parameters.clone(), - ..allowed_payment_methods.parameters - }, - ..allowed_payment_methods - }, - ) - .collect(); let required_amount_type = StringMajorUnitForConnector; let google_pay_amount = required_amount_type .convert( @@ -945,39 +928,186 @@ fn create_gpay_session_token( false }; - Ok(types::PaymentsSessionRouterData { - response: Ok(types::PaymentsResponseData::SessionResponse { - session_token: payment_types::SessionToken::GooglePay(Box::new( - payment_types::GpaySessionTokenResponse::GooglePaySession( - payment_types::GooglePaySessionResponse { - merchant_info: gpay_data.data.merchant_info, - allowed_payment_methods: gpay_allowed_payment_methods, - transaction_info, - connector: connector.connector_name.to_string(), - sdk_next_action: payment_types::SdkNextAction { - next_action: payment_types::NextActionCall::Confirm, - }, - delayed_session_token: false, - secrets: None, - shipping_address_required: required_shipping_contact_fields, - // We pass Email as a required field irrespective of - // collect_billing_details_from_wallet_connector or - // collect_shipping_details_from_wallet_connector as it is common to both. - email_required: required_shipping_contact_fields - || is_billing_details_required, - shipping_address_parameters: - api_models::payments::GpayShippingAddressParameters { - phone_number_required: required_shipping_contact_fields, + if connector_wallets_details.google_pay.is_some() { + let gpay_data = router_data + .connector_wallets_details + .clone() + .parse_value::("GooglePayWalletDetails") + .change_context(errors::ConnectorError::NoConnectorWalletDetails) + .attach_printable(format!( + "cannot parse gpay connector_wallets_details from the given value {:?}", + router_data.connector_wallets_details + )) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_wallets_details".to_string(), + expected_format: "gpay_connector_wallets_details_format".to_string(), + })?; + + let payment_types::GooglePayProviderDetails::GooglePayMerchantDetails(gpay_info) = + gpay_data.google_pay.provider_details.clone(); + + let gpay_allowed_payment_methods = get_allowed_payment_methods_from_cards( + gpay_data, + &gpay_info.merchant_info.tokenization_specification, + is_billing_details_required, + )?; + + Ok(types::PaymentsSessionRouterData { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: payment_types::SessionToken::GooglePay(Box::new( + payment_types::GpaySessionTokenResponse::GooglePaySession( + payment_types::GooglePaySessionResponse { + merchant_info: payment_types::GpayMerchantInfo { + merchant_name: gpay_info.merchant_info.merchant_name, + merchant_id: gpay_info.merchant_info.merchant_id, + }, + allowed_payment_methods: vec![gpay_allowed_payment_methods], + transaction_info, + connector: connector.connector_name.to_string(), + sdk_next_action: payment_types::SdkNextAction { + next_action: payment_types::NextActionCall::Confirm, }, + delayed_session_token: false, + secrets: None, + shipping_address_required: required_shipping_contact_fields, + // We pass Email as a required field irrespective of + // collect_billing_details_from_wallet_connector or + // collect_shipping_details_from_wallet_connector as it is common to both. + email_required: required_shipping_contact_fields + || is_billing_details_required, + shipping_address_parameters: + api_models::payments::GpayShippingAddressParameters { + phone_number_required: required_shipping_contact_fields, + }, + }, + ), + )), + }), + ..router_data.clone() + }) + } else { + let billing_address_parameters = is_billing_details_required.then_some( + payment_types::GpayBillingAddressParameters { + phone_number_required: is_billing_details_required, + format: payment_types::GpayBillingAddressFormat::FULL, + }, + ); + + let gpay_data = connector_metadata + .clone() + .parse_value::("GpaySessionTokenData") + .change_context(errors::ConnectorError::NoConnectorMetaData) + .attach_printable(format!( + "cannot parse gpay metadata from the given value {connector_metadata:?}" + )) + .change_context(errors::ApiErrorResponse::InvalidDataFormat { + field_name: "connector_metadata".to_string(), + expected_format: "gpay_metadata_format".to_string(), + })?; + + let gpay_allowed_payment_methods = gpay_data + .data + .allowed_payment_methods + .into_iter() + .map( + |allowed_payment_methods| payment_types::GpayAllowedPaymentMethods { + parameters: payment_types::GpayAllowedMethodsParameters { + billing_address_required: Some(is_billing_details_required), + billing_address_parameters: billing_address_parameters.clone(), + ..allowed_payment_methods.parameters }, - ), - )), - }), - ..router_data.clone() - }) + ..allowed_payment_methods + }, + ) + .collect(); + + Ok(types::PaymentsSessionRouterData { + response: Ok(types::PaymentsResponseData::SessionResponse { + session_token: payment_types::SessionToken::GooglePay(Box::new( + payment_types::GpaySessionTokenResponse::GooglePaySession( + payment_types::GooglePaySessionResponse { + merchant_info: gpay_data.data.merchant_info, + allowed_payment_methods: gpay_allowed_payment_methods, + transaction_info, + connector: connector.connector_name.to_string(), + sdk_next_action: payment_types::SdkNextAction { + next_action: payment_types::NextActionCall::Confirm, + }, + delayed_session_token: false, + secrets: None, + shipping_address_required: required_shipping_contact_fields, + // We pass Email as a required field irrespective of + // collect_billing_details_from_wallet_connector or + // collect_shipping_details_from_wallet_connector as it is common to both. + email_required: required_shipping_contact_fields + || is_billing_details_required, + shipping_address_parameters: + api_models::payments::GpayShippingAddressParameters { + phone_number_required: required_shipping_contact_fields, + }, + }, + ), + )), + }), + ..router_data.clone() + }) + } } } +/// Card Type for Google Pay Allowerd Payment Methods +pub(crate) const CARD: &str = "CARD"; + +fn get_allowed_payment_methods_from_cards( + gpay_info: payment_types::GooglePayWalletDetails, + gpay_token_specific_data: &payment_types::GooglePayTokenizationSpecification, + is_billing_details_required: bool, +) -> RouterResult { + let billing_address_parameters = + is_billing_details_required.then_some(payment_types::GpayBillingAddressParameters { + phone_number_required: is_billing_details_required, + format: payment_types::GpayBillingAddressFormat::FULL, + }); + + let protocol_version: Option = gpay_token_specific_data + .parameters + .public_key + .as_ref() + .map(|_| PROTOCOL.to_string()); + + Ok(payment_types::GpayAllowedPaymentMethods { + parameters: payment_types::GpayAllowedMethodsParameters { + billing_address_required: Some(is_billing_details_required), + billing_address_parameters: billing_address_parameters.clone(), + ..gpay_info.google_pay.cards + }, + payment_method_type: CARD.to_string(), + tokenization_specification: payment_types::GpayTokenizationSpecification { + token_specification_type: gpay_token_specific_data.tokenization_type.to_string(), + parameters: payment_types::GpayTokenParameters { + protocol_version, + public_key: gpay_token_specific_data.parameters.public_key.clone(), + gateway: gpay_token_specific_data.parameters.gateway.clone(), + gateway_merchant_id: gpay_token_specific_data + .parameters + .gateway_merchant_id + .clone() + .expose_option(), + stripe_publishable_key: gpay_token_specific_data + .parameters + .stripe_publishable_key + .clone() + .expose_option(), + stripe_version: gpay_token_specific_data + .parameters + .stripe_version + .clone() + .expose_option(), + }, + }, + }) +} + fn is_session_response_delayed( state: &routes::SessionState, connector: &api::ConnectorData, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 28d843e2350..39bbef1f3d7 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -47,8 +47,8 @@ use openssl::{ }; #[cfg(feature = "v2")] use redis_interface::errors::RedisError; -use ring::hmac; use router_env::{instrument, logger, tracing}; +use serde::{Deserialize, Serialize}; use uuid::Uuid; use x509_parser::parse_x509_certificate; @@ -5267,7 +5267,7 @@ where Ok(connector_data_list) } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Serialize, Deserialize)] pub struct ApplePayData { version: masking::Secret, data: masking::Secret, @@ -5275,7 +5275,7 @@ pub struct ApplePayData { header: ApplePayHeader, } -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApplePayHeader { ephemeral_public_key: masking::Secret, @@ -5430,13 +5430,10 @@ impl ApplePayData { } } -pub(crate) const SENDER_ID: &[u8] = b"Google"; -pub(crate) const PROTOCOL: &str = "ECv2"; - // Structs for keys and the main decryptor pub struct GooglePayTokenDecryptor { root_signing_keys: Vec, - recipient_id: Option>, + recipient_id: masking::Secret, private_key: PKey, } @@ -5543,21 +5540,24 @@ fn filter_root_signing_keys( impl GooglePayTokenDecryptor { pub fn new( root_keys: masking::Secret, - recipient_id: Option>, + recipient_id: masking::Secret, private_key: masking::Secret, ) -> CustomResult { // base64 decode the private key let decoded_key = BASE64_ENGINE .decode(private_key.expose()) .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; + // base64 decode the root signing keys + let decoded_root_signing_keys = BASE64_ENGINE + .decode(root_keys.expose()) + .change_context(errors::GooglePayDecryptionError::Base64DecodingFailed)?; // create a private key from the decoded key let private_key = PKey::private_key_from_pkcs8(&decoded_key) .change_context(errors::GooglePayDecryptionError::KeyDeserializationFailed) .attach_printable("cannot convert private key from decode_key")?; // parse the root signing keys - let root_keys_vector: Vec = root_keys - .expose() + let root_keys_vector: Vec = decoded_root_signing_keys .parse_struct("GooglePayRootSigningKey") .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; @@ -5663,13 +5663,13 @@ impl GooglePayTokenDecryptor { } // get the sender id i.e. Google - let sender_id = String::from_utf8(SENDER_ID.to_vec()) + let sender_id = String::from_utf8(consts::SENDER_ID.to_vec()) .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; // construct the signed data let signed_data = self.construct_signed_data_for_intermediate_signing_key_verification( &sender_id, - PROTOCOL, + consts::PROTOCOL, encrypted_data.intermediate_signing_key.signed_key.peek(), )?; @@ -5770,7 +5770,7 @@ impl GooglePayTokenDecryptor { .change_context(errors::GooglePayDecryptionError::DerivingEcKeyFailed)?; // get the sender id i.e. Google - let sender_id = String::from_utf8(SENDER_ID.to_vec()) + let sender_id = String::from_utf8(consts::SENDER_ID.to_vec()) .change_context(errors::GooglePayDecryptionError::DeserializationFailed)?; // serialize the signed message to string @@ -5780,7 +5780,7 @@ impl GooglePayTokenDecryptor { // construct the signed data let signed_data = self.construct_signed_data_for_signature_verification( &sender_id, - PROTOCOL, + consts::PROTOCOL, &signed_message, )?; @@ -5827,11 +5827,7 @@ impl GooglePayTokenDecryptor { protocol_version: &str, signed_key: &str, ) -> CustomResult, errors::GooglePayDecryptionError> { - let recipient_id = self - .recipient_id - .clone() - .ok_or(errors::GooglePayDecryptionError::RecipientIdNotFound)? - .expose(); + let recipient_id = self.recipient_id.clone().expose(); let length_of_sender_id = u32::try_from(sender_id.len()) .change_context(errors::GooglePayDecryptionError::ParsingFailed)?; let length_of_recipient_id = u32::try_from(recipient_id.len()) @@ -5911,13 +5907,14 @@ impl GooglePayTokenDecryptor { // derive 64 bytes for the output key (symmetric encryption + MAC key) let mut output_key = vec![0u8; 64]; - hkdf.expand(SENDER_ID, &mut output_key).map_err(|err| { - logger::error!( + hkdf.expand(consts::SENDER_ID, &mut output_key) + .map_err(|err| { + logger::error!( "Failed to derive the shared ephemeral key for Google Pay decryption flow: {:?}", err ); - report!(errors::GooglePayDecryptionError::DerivingSharedEphemeralKeyFailed) - })?; + report!(errors::GooglePayDecryptionError::DerivingSharedEphemeralKeyFailed) + })?; Ok(output_key) } @@ -5930,8 +5927,8 @@ impl GooglePayTokenDecryptor { tag: &[u8], encrypted_message: &[u8], ) -> CustomResult<(), errors::GooglePayDecryptionError> { - let hmac_key = hmac::Key::new(hmac::HMAC_SHA256, mac_key); - hmac::verify(&hmac_key, encrypted_message, tag) + let hmac_key = ring::hmac::Key::new(ring::hmac::HMAC_SHA256, mac_key); + ring::hmac::verify(&hmac_key, encrypted_message, tag) .change_context(errors::GooglePayDecryptionError::HmacVerificationFailed) } @@ -6024,7 +6021,7 @@ pub fn decrypt_paze_token( Ok(parsed_decrypted) } -#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct JwsBody { pub payload_id: String, From b09905ecb4c7b33576b3ca1f13affe5341ea6e6f Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Thu, 13 Feb 2025 15:13:12 +0530 Subject: [PATCH 092/133] feat(connector): [Moneris] add template code (#7216) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 2 + config/deployments/integration_test.toml | 1 + config/deployments/production.toml | 1 + config/deployments/sandbox.toml | 1 + config/development.toml | 2 + config/docker_compose.toml | 2 + crates/common_enums/src/connector_enums.rs | 3 + .../hyperswitch_connectors/src/connectors.rs | 7 +- .../src/connectors/moneris.rs | 565 ++++++++++++++++++ .../src/connectors/moneris/transformers.rs | 228 +++++++ .../src/default_implementations.rs | 35 ++ .../src/default_implementations_v2.rs | 22 + crates/hyperswitch_interfaces/src/configs.rs | 1 + crates/router/src/connector.rs | 8 +- .../connector_integration_v2_impls.rs | 3 + crates/router/src/core/payments/flows.rs | 3 + crates/router/src/types/api.rs | 1 + crates/router/src/types/transformers.rs | 1 + crates/router/tests/connectors/main.rs | 1 + crates/router/tests/connectors/moneris.rs | 421 +++++++++++++ .../router/tests/connectors/sample_auth.toml | 3 + crates/test_utils/src/connector_auth.rs | 1 + loadtest/config/development.toml | 2 + scripts/add_connector.sh | 2 +- 24 files changed, 1308 insertions(+), 8 deletions(-) create mode 100644 crates/hyperswitch_connectors/src/connectors/moneris.rs create mode 100644 crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs create mode 100644 crates/router/tests/connectors/moneris.rs diff --git a/config/config.example.toml b/config/config.example.toml index 05a0e069a16..64b382eebae 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -227,6 +227,7 @@ jpmorgan.secondary_base_url= "https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" +moneris.base_url = "https://api.sb.moneris.io" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netcetera-cloud-payment.ch" @@ -327,6 +328,7 @@ cards = [ "gpayments", "helcim", "mollie", + "moneris", "paypal", "shift4", "square", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index e5badf2cec7..952cdff80e6 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -74,6 +74,7 @@ klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +moneris.base_url = "https://api.sb.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 32638988f6d..ac5a48007e1 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -78,6 +78,7 @@ klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +moneris.base_url = "https://api.sb.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://api.payengine.de/v1" nexixpay.base_url = "https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index f91cba9d992..42ec46a6cad 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -78,6 +78,7 @@ klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" +moneris.base_url = "https://api.sb.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://apitest.payengine.de/v1" nexixpay.base_url = "https://xpaysandbox.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/config/development.toml b/config/development.toml index c2f284bea2c..7c4af97971f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -181,6 +181,7 @@ cards = [ "itaubank", "jpmorgan", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -296,6 +297,7 @@ jpmorgan.secondary_base_url = "https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" +moneris.base_url = "https://api.sb.moneris.io" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netcetera-cloud-payment.ch" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 83e64ddc57a..48df4c2c93b 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -159,6 +159,7 @@ jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" +moneris.base_url = "https://api.sb.moneris.io" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netcetera-cloud-payment.ch" @@ -261,6 +262,7 @@ cards = [ "itaubank", "jpmorgan", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 61dbfd3f7fa..ce7335ddeae 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -93,6 +93,7 @@ pub enum RoutableConnectors { Klarna, Mifinity, Mollie, + // Moneris, Multisafepay, Nexinets, Nexixpay, @@ -231,6 +232,7 @@ pub enum Connector { Klarna, Mifinity, Mollie, + // Moneris, Multisafepay, Netcetera, Nexinets, @@ -380,6 +382,7 @@ impl Connector { | Self::Klarna | Self::Mifinity | Self::Mollie + // | Self::Moneris | Self::Multisafepay | Self::Nexinets | Self::Nexixpay diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index a236309e88a..0b6042ef931 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -37,6 +37,7 @@ pub mod jpmorgan; pub mod klarna; pub mod mifinity; pub mod mollie; +pub mod moneris; pub mod multisafepay; pub mod nexinets; pub mod nexixpay; @@ -77,9 +78,9 @@ pub use self::{ fiserv::Fiserv, fiservemea::Fiservemea, fiuu::Fiuu, forte::Forte, getnet::Getnet, globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, inespay::Inespay, itaubank::Itaubank, jpmorgan::Jpmorgan, klarna::Klarna, - mifinity::Mifinity, mollie::Mollie, multisafepay::Multisafepay, nexinets::Nexinets, - nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, nuvei::Nuvei, paybox::Paybox, - payeezy::Payeezy, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, + mifinity::Mifinity, mollie::Mollie, moneris::Moneris, multisafepay::Multisafepay, + nexinets::Nexinets, nexixpay::Nexixpay, nomupay::Nomupay, novalnet::Novalnet, nuvei::Nuvei, + paybox::Paybox, payeezy::Payeezy, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, razorpay::Razorpay, redsys::Redsys, shift4::Shift4, square::Square, stax::Stax, taxjar::Taxjar, thunes::Thunes, tsys::Tsys, unified_authentication_service::UnifiedAuthenticationService, volt::Volt, diff --git a/crates/hyperswitch_connectors/src/connectors/moneris.rs b/crates/hyperswitch_connectors/src/connectors/moneris.rs new file mode 100644 index 00000000000..d543a0be72c --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/moneris.rs @@ -0,0 +1,565 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as moneris; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Moneris { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Moneris { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Moneris {} +impl api::PaymentSession for Moneris {} +impl api::ConnectorAccessToken for Moneris {} +impl api::MandateSetup for Moneris {} +impl api::PaymentAuthorize for Moneris {} +impl api::PaymentSync for Moneris {} +impl api::PaymentCapture for Moneris {} +impl api::PaymentVoid for Moneris {} +impl api::Refund for Moneris {} +impl api::RefundExecute for Moneris {} +impl api::RefundSync for Moneris {} +impl api::PaymentToken for Moneris {} + +impl ConnectorIntegration + for Moneris +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Moneris +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } +} + +impl ConnectorCommon for Moneris { + fn id(&self) -> &'static str { + "moneris" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.moneris.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = moneris::MonerisAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: moneris::MonerisErrorResponse = res + .response + .parse_struct("MonerisErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Moneris { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Moneris { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Moneris {} + +impl ConnectorIntegration for Moneris {} + +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = moneris::MonerisRouterData::from((amount, req)); + let connector_req = moneris::MonerisPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("Moneris PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("moneris PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("Moneris PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Moneris {} + +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = moneris::MonerisRouterData::from((refund_amount, req)); + let connector_req = moneris::MonerisRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: moneris::RefundResponse = res + .response + .parse_struct("moneris RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::RefundResponse = res + .response + .parse_struct("moneris RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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] +impl webhooks::IncomingWebhook for Moneris { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Moneris {} diff --git a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs new file mode 100644 index 00000000000..0cc255d85bf --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs @@ -0,0 +1,228 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct MonerisRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for MonerisRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct MonerisPaymentsRequest { + amount: StringMinorUnit, + card: MonerisCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct MonerisCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &MonerisRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = MonerisCard { + number: req_card.card_number, + expiry_month: req_card.card_exp_month, + expiry_year: req_card.card_exp_year, + cvc: req_card.card_cvc, + complete: item.router_data.request.is_auto_capture()?, + }; + Ok(Self { + amount: item.amount.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct MonerisAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&ConnectorAuthType> for MonerisAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum MonerisPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: MonerisPaymentStatus) -> Self { + match item { + MonerisPaymentStatus::Succeeded => Self::Charged, + MonerisPaymentStatus::Failed => Self::Failure, + MonerisPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MonerisPaymentsResponse { + status: MonerisPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct MonerisRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&MonerisRouterData<&RefundsRouterData>> for MonerisRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &MonerisRouterData<&RefundsRouterData>) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct MonerisErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index f2f7e6b441b..a88de8331c3 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -143,6 +143,7 @@ default_imp_for_authorize_session_token!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -216,6 +217,7 @@ default_imp_for_calculate_tax!( connectors::Klarna, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Nexinets, connectors::Nexixpay, @@ -302,6 +304,7 @@ default_imp_for_session_update!( connectors::Taxjar, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, @@ -387,6 +390,7 @@ default_imp_for_post_session_tokens!( connectors::Taxjar, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, @@ -463,6 +467,7 @@ default_imp_for_complete_authorize!( connectors::Jpmorgan, connectors::Klarna, connectors::Mifinity, + connectors::Moneris, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, @@ -552,6 +557,7 @@ default_imp_for_incremental_authorization!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -625,6 +631,7 @@ default_imp_for_create_customer!( connectors::Klarna, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Nomupay, connectors::Novalnet, @@ -705,6 +712,7 @@ default_imp_for_connector_redirect_response!( connectors::Jpmorgan, connectors::Klarna, connectors::Mifinity, + connectors::Moneris, connectors::Multisafepay, connectors::Nexinets, connectors::Nexixpay, @@ -791,6 +799,7 @@ default_imp_for_pre_processing_steps!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -876,6 +885,7 @@ default_imp_for_post_processing_steps!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -962,6 +972,7 @@ default_imp_for_approve!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1048,6 +1059,7 @@ default_imp_for_reject!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1134,6 +1146,7 @@ default_imp_for_webhook_source_verification!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1221,6 +1234,7 @@ default_imp_for_accept_dispute!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1307,6 +1321,7 @@ default_imp_for_submit_evidence!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1393,6 +1408,7 @@ default_imp_for_defend_dispute!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1488,6 +1504,7 @@ default_imp_for_file_upload!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1555,6 +1572,7 @@ default_imp_for_payouts!( connectors::Klarna, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Nexinets, connectors::Nexixpay, @@ -1653,6 +1671,7 @@ default_imp_for_payouts_create!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1741,6 +1760,7 @@ default_imp_for_payouts_retrieve!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1829,6 +1849,7 @@ default_imp_for_payouts_eligibility!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1916,6 +1937,7 @@ default_imp_for_payouts_fulfill!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2004,6 +2026,7 @@ default_imp_for_payouts_cancel!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2092,6 +2115,7 @@ default_imp_for_payouts_quote!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2180,6 +2204,7 @@ default_imp_for_payouts_recipient!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2268,6 +2293,7 @@ default_imp_for_payouts_recipient_account!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2356,6 +2382,7 @@ default_imp_for_frm_sale!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2444,6 +2471,7 @@ default_imp_for_frm_checkout!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2532,6 +2560,7 @@ default_imp_for_frm_transaction!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2620,6 +2649,7 @@ default_imp_for_frm_fulfillment!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2708,6 +2738,7 @@ default_imp_for_frm_record_return!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2792,6 +2823,7 @@ default_imp_for_revoking_mandates!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2876,6 +2908,7 @@ default_imp_for_uas_pre_authentication!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Paybox, connectors::Placetopay, @@ -2960,6 +2993,7 @@ default_imp_for_uas_post_authentication!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Paybox, connectors::Placetopay, @@ -3044,6 +3078,7 @@ default_imp_for_uas_authentication!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Paybox, connectors::Placetopay, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index 93623f3d7f0..d0ffd3a230c 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -254,6 +254,7 @@ default_imp_for_new_connector_integration_payment!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -341,6 +342,7 @@ default_imp_for_new_connector_integration_refund!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -423,6 +425,7 @@ default_imp_for_new_connector_integration_connector_access_token!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -510,6 +513,7 @@ default_imp_for_new_connector_integration_accept_dispute!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -596,6 +600,7 @@ default_imp_for_new_connector_integration_submit_evidence!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -683,6 +688,7 @@ default_imp_for_new_connector_integration_defend_dispute!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -780,6 +786,7 @@ default_imp_for_new_connector_integration_file_upload!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -869,6 +876,7 @@ default_imp_for_new_connector_integration_payouts_create!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -958,6 +966,7 @@ default_imp_for_new_connector_integration_payouts_eligibility!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1047,6 +1056,7 @@ default_imp_for_new_connector_integration_payouts_fulfill!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1136,6 +1146,7 @@ default_imp_for_new_connector_integration_payouts_cancel!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1225,6 +1236,7 @@ default_imp_for_new_connector_integration_payouts_quote!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1314,6 +1326,7 @@ default_imp_for_new_connector_integration_payouts_recipient!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1403,6 +1416,7 @@ default_imp_for_new_connector_integration_payouts_sync!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1492,6 +1506,7 @@ default_imp_for_new_connector_integration_payouts_recipient_account!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1579,6 +1594,7 @@ default_imp_for_new_connector_integration_webhook_source_verification!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1668,6 +1684,7 @@ default_imp_for_new_connector_integration_frm_sale!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1757,6 +1774,7 @@ default_imp_for_new_connector_integration_frm_checkout!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1846,6 +1864,7 @@ default_imp_for_new_connector_integration_frm_transaction!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -1935,6 +1954,7 @@ default_imp_for_new_connector_integration_frm_fulfillment!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2024,6 +2044,7 @@ default_imp_for_new_connector_integration_frm_record_return!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, @@ -2110,6 +2131,7 @@ default_imp_for_new_connector_integration_revoking_mandates!( connectors::Prophetpay, connectors::Mifinity, connectors::Mollie, + connectors::Moneris, connectors::Multisafepay, connectors::Rapyd, connectors::Razorpay, diff --git a/crates/hyperswitch_interfaces/src/configs.rs b/crates/hyperswitch_interfaces/src/configs.rs index 8781060eb9e..d5656f3a477 100644 --- a/crates/hyperswitch_interfaces/src/configs.rs +++ b/crates/hyperswitch_interfaces/src/configs.rs @@ -56,6 +56,7 @@ pub struct Connectors { pub klarna: ConnectorParams, pub mifinity: ConnectorParams, pub mollie: ConnectorParams, + pub moneris: ConnectorParams, pub multisafepay: ConnectorParams, pub netcetera: ConnectorParams, pub nexinets: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index c8ac040ff8b..4bf57cb16f0 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -38,10 +38,10 @@ pub use hyperswitch_connectors::connectors::{ getnet, getnet::Getnet, globalpay, globalpay::Globalpay, globepay, globepay::Globepay, gocardless, gocardless::Gocardless, helcim, helcim::Helcim, iatapay, iatapay::Iatapay, inespay, inespay::Inespay, itaubank, itaubank::Itaubank, jpmorgan, jpmorgan::Jpmorgan, klarna, - klarna::Klarna, mifinity, mifinity::Mifinity, mollie, mollie::Mollie, multisafepay, - multisafepay::Multisafepay, nexinets, nexinets::Nexinets, nexixpay, nexixpay::Nexixpay, - nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, nuvei, nuvei::Nuvei, paybox, - paybox::Paybox, payeezy, payeezy::Payeezy, payu, payu::Payu, placetopay, + klarna::Klarna, mifinity, mifinity::Mifinity, mollie, mollie::Mollie, moneris, + moneris::Moneris, multisafepay, multisafepay::Multisafepay, nexinets, nexinets::Nexinets, + nexixpay, nexixpay::Nexixpay, nomupay, nomupay::Nomupay, novalnet, novalnet::Novalnet, nuvei, + nuvei::Nuvei, paybox, paybox::Paybox, payeezy, payeezy::Payeezy, payu, payu::Payu, placetopay, placetopay::Placetopay, powertranz, powertranz::Powertranz, prophetpay, prophetpay::Prophetpay, rapyd, rapyd::Rapyd, razorpay, razorpay::Razorpay, redsys, redsys::Redsys, shift4, shift4::Shift4, square, square::Square, stax, stax::Stax, taxjar, taxjar::Taxjar, thunes, diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index b9595c1b23d..03006706e25 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1014,6 +1014,7 @@ default_imp_for_new_connector_integration_payouts!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Netcetera, connector::Nexinets, @@ -1479,6 +1480,7 @@ default_imp_for_new_connector_integration_frm!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Netcetera, connector::Nexinets, @@ -1854,6 +1856,7 @@ default_imp_for_new_connector_integration_connector_authentication!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Netcetera, connector::Nexinets, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index e311c37ffab..1aa5ed66294 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -434,6 +434,7 @@ default_imp_for_connector_request_id!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Netcetera, connector::Nexixpay, @@ -1393,6 +1394,7 @@ default_imp_for_fraud_check!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Netcetera, connector::Nexinets, @@ -1928,6 +1930,7 @@ default_imp_for_connector_authentication!( connector::Klarna, connector::Mifinity, connector::Mollie, + connector::Moneris, connector::Multisafepay, connector::Nexinets, connector::Nexixpay, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index ab3572d2661..11f2a1902d0 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -459,6 +459,7 @@ impl ConnectorData { Ok(ConnectorEnum::Old(Box::new(connector::Klarna::new()))) } enums::Connector::Mollie => { + // enums::Connector::Moneris => Ok(ConnectorEnum::Old(Box::new(connector::Moneris))), Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) } enums::Connector::Nexixpay => { diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 40d07490b83..99ed67ff836 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -263,6 +263,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, + // api_enums::Connector::Moneris => Self::Moneris, api_enums::Connector::Multisafepay => Self::Multisafepay, api_enums::Connector::Netcetera => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 7e9e49c06a0..f2fdfddfd63 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -50,6 +50,7 @@ mod itaubank; mod jpmorgan; mod mifinity; mod mollie; +mod moneris; mod multisafepay; mod netcetera; mod nexinets; diff --git a/crates/router/tests/connectors/moneris.rs b/crates/router/tests/connectors/moneris.rs new file mode 100644 index 00000000000..ac98cc17f3d --- /dev/null +++ b/crates/router/tests/connectors/moneris.rs @@ -0,0 +1,421 @@ +use hyperswitch_domain_models::payment_method_data::{Card, PaymentMethodData}; +use masking::Secret; +use router::types::{self, api, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct MonerisTest; +impl ConnectorActions for MonerisTest {} +impl utils::Connector for MonerisTest { + fn get_data(&self) -> api::ConnectorData { + use router::connector::Moneris; + utils::construct_connector_data_old( + Box::new(Moneris::new()), + types::Connector::Plaid, + api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .moneris + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "moneris".to_string() + } +} + +static CONNECTOR: MonerisTest = MonerisTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: PaymentMethodData::Card(Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index bf8b35d2fe0..4677417104c 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -310,4 +310,7 @@ api_key="API Key" api_key="API Key" [chargebee] +api_key= "API Key" + +[moneris] api_key= "API Key" \ No newline at end of file diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 8e733330517..30de321a4b6 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -57,6 +57,7 @@ pub struct ConnectorAuthentication { pub jpmorgan: Option, pub mifinity: Option, pub mollie: Option, + pub moneris: Option, pub multisafepay: Option, pub netcetera: Option, pub nexinets: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index a57007f0d3a..4b7d194b6f6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -125,6 +125,7 @@ jpmorgan.secondary_base_url="https://id.payments.jpmorgan.com" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" +moneris.base_url = "https://api.sb.moneris.io" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" netcetera.base_url = "https://{{merchant_endpoint_prefix}}.3ds-server.prev.netcetera-cloud-payment.ch" @@ -227,6 +228,7 @@ cards = [ "itaubank", "jpmorgan", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 049b4db3f8f..a2bf5872c07 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim iatapay inespay itaubank jpmorgan klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal payu placetopay plaid powertranz prophetpay rapyd razorpay redsys shift4 square stax stripe taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp From 3916ba6e3d4707b3a1f28439a314e6ea098597f2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 14 Feb 2025 00:27:00 +0000 Subject: [PATCH 093/133] chore(version): 2025.02.14.0 --- CHANGELOG.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 57ff6ff0fd1..78cce8ecebf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,21 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.14.0 + +### Features + +- **connector:** [Moneris] add template code ([#7216](https://github.com/juspay/hyperswitch/pull/7216)) ([`b09905e`](https://github.com/juspay/hyperswitch/commit/b09905ecb4c7b33576b3ca1f13affe5341ea6e6f)) +- **core:** Add support to generate session token response from both `connector_wallets_details` and `metadata` ([#7140](https://github.com/juspay/hyperswitch/pull/7140)) ([`66d9c73`](https://github.com/juspay/hyperswitch/commit/66d9c731f528cd33a1a94815485d6efceb493742)) + +### Bug Fixes + +- **connectors:** [fiuu] zero amount mandate flow for wallets ([#7251](https://github.com/juspay/hyperswitch/pull/7251)) ([`6aac16e`](https://github.com/juspay/hyperswitch/commit/6aac16e0c997d36e653f91be0f2a6660a3378dd5)) + +**Full Changelog:** [`2025.02.13.0...2025.02.14.0`](https://github.com/juspay/hyperswitch/compare/2025.02.13.0...2025.02.14.0) + +- - - + ## 2025.02.13.0 ### Features From 12ef8ee0fc63829429697c42b98f4c773f12cade Mon Sep 17 00:00:00 2001 From: Mani Chandra <84711804+ThisIsMani@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:48:12 +0530 Subject: [PATCH 094/133] refactor(payments): Add platform merchant account checks for payment intent (#7204) --- .../payments/operations/payment_approve.rs | 6 +++- .../payments/operations/payment_cancel.rs | 6 +++- .../payments/operations/payment_capture.rs | 6 +++- .../payments/operations/payment_capture_v2.rs | 11 +++++-- .../operations/payment_complete_authorize.rs | 3 ++ .../payments/operations/payment_confirm.rs | 2 ++ .../operations/payment_confirm_intent.rs | 2 ++ .../core/payments/operations/payment_get.rs | 11 +++++-- .../payments/operations/payment_get_intent.rs | 6 +++- .../operations/payment_post_session_tokens.rs | 2 ++ .../payments/operations/payment_reject.rs | 6 +++- .../payments/operations/payment_session.rs | 2 ++ .../operations/payment_session_intent.rs | 4 ++- .../core/payments/operations/payment_start.rs | 2 ++ .../payments/operations/payment_status.rs | 9 ++++- .../payments/operations/payment_update.rs | 2 ++ .../operations/payment_update_intent.rs | 8 +++-- .../payments_incremental_authorization.rs | 6 +++- .../payments/operations/tax_calculation.rs | 2 ++ crates/router/src/core/utils.rs | 33 +++++++++++++++++++ crates/router/src/routes/payment_methods.rs | 1 + 21 files changed, 116 insertions(+), 14 deletions(-) diff --git a/crates/router/src/core/payments/operations/payment_approve.rs b/crates/router/src/core/payments/operations/payment_approve.rs index c830b7618d0..bdb0548d6a4 100644 --- a/crates/router/src/core/payments/operations/payment_approve.rs +++ b/crates/router/src/core/payments/operations/payment_approve.rs @@ -11,6 +11,7 @@ use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, payments::{helpers, operations, PaymentData}, + utils::ValidatePlatformMerchant, }, events::audit_events::{AuditEvent, AuditEventType}, routes::{app::ReqState, SessionState}, @@ -45,7 +46,7 @@ impl GetTracker, api::PaymentsCaptureR key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCaptureRequest, PaymentData>, > { @@ -70,6 +71,9 @@ impl GetTracker, api::PaymentsCaptureR .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[IntentStatus::Failed, IntentStatus::Succeeded], diff --git a/crates/router/src/core/payments/operations/payment_cancel.rs b/crates/router/src/core/payments/operations/payment_cancel.rs index c6679e481f1..b41247003bd 100644 --- a/crates/router/src/core/payments/operations/payment_cancel.rs +++ b/crates/router/src/core/payments/operations/payment_cancel.rs @@ -12,6 +12,7 @@ use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, payments::{helpers, operations, PaymentData}, + utils::ValidatePlatformMerchant, }, events::audit_events::{AuditEvent, AuditEventType}, routes::{app::ReqState, SessionState}, @@ -46,7 +47,7 @@ impl GetTracker, api::PaymentsCancelRe key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsCancelRequest, PaymentData>, > { @@ -70,6 +71,9 @@ impl GetTracker, api::PaymentsCancelRe .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[ diff --git a/crates/router/src/core/payments/operations/payment_capture.rs b/crates/router/src/core/payments/operations/payment_capture.rs index ebe49f59f64..2bb8dd279e5 100644 --- a/crates/router/src/core/payments/operations/payment_capture.rs +++ b/crates/router/src/core/payments/operations/payment_capture.rs @@ -11,6 +11,7 @@ use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, payments::{self, helpers, operations, types::MultipleCaptureData}, + utils::ValidatePlatformMerchant, }, events::audit_events::{AuditEvent, AuditEventType}, routes::{app::ReqState, SessionState}, @@ -45,7 +46,7 @@ impl GetTracker, api::Paymen key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -76,6 +77,9 @@ impl GetTracker, api::Paymen .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + payment_attempt = db .find_payment_attempt_by_payment_id_merchant_id_attempt_id( &payment_intent.payment_id, diff --git a/crates/router/src/core/payments/operations/payment_capture_v2.rs b/crates/router/src/core/payments/operations/payment_capture_v2.rs index 81ffa8e992f..93b7ffa6c19 100644 --- a/crates/router/src/core/payments/operations/payment_capture_v2.rs +++ b/crates/router/src/core/payments/operations/payment_capture_v2.rs @@ -8,7 +8,11 @@ use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; use crate::{ core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, - payments::operations::{self, ValidateStatusForOperation}, + payments::{ + helpers, + operations::{self, ValidateStatusForOperation}, + }, + utils::ValidatePlatformMerchant, }, routes::{app::ReqState, SessionState}, types::{ @@ -142,7 +146,7 @@ impl GetTracker, PaymentsCaptureReques _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); @@ -154,6 +158,9 @@ impl GetTracker, PaymentsCaptureReques .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + self.validate_status_for_operation(payment_intent.status)?; let active_attempt_id = payment_intent 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 72c04c6a549..f1279f394b2 100644 --- a/crates/router/src/core/payments/operations/payment_complete_authorize.rs +++ b/crates/router/src/core/payments/operations/payment_complete_authorize.rs @@ -72,6 +72,9 @@ impl GetTracker, api::PaymentsRequest> ) .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + + // TODO (#7195): Add platform merchant account validation once client_secret auth is solved + payment_intent.setup_future_usage = request .setup_future_usage .or(payment_intent.setup_future_usage); diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 64191f77c6d..84ff9933875 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -106,6 +106,8 @@ impl GetTracker, api::PaymentsRequest> .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once client_secret auth is solved + if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 043ee3378b3..2e4c0190adb 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -171,6 +171,8 @@ impl GetTracker, PaymentsConfir .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + self.validate_status_for_operation(payment_intent.status)?; let client_secret = header_payload .client_secret diff --git a/crates/router/src/core/payments/operations/payment_get.rs b/crates/router/src/core/payments/operations/payment_get.rs index 189439ec967..fdf5350bed8 100644 --- a/crates/router/src/core/payments/operations/payment_get.rs +++ b/crates/router/src/core/payments/operations/payment_get.rs @@ -9,7 +9,11 @@ use super::{Domain, GetTracker, Operation, UpdateTracker, ValidateRequest}; use crate::{ core::{ errors::{self, CustomResult, RouterResult, StorageErrorExt}, - payments::operations::{self, ValidateStatusForOperation}, + payments::{ + helpers, + operations::{self, ValidateStatusForOperation}, + }, + utils::ValidatePlatformMerchant, }, routes::{app::ReqState, SessionState}, types::{ @@ -119,7 +123,7 @@ impl GetTracker, PaymentsRetriev _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); @@ -131,6 +135,9 @@ impl GetTracker, PaymentsRetriev .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + let payment_attempt = payment_intent .active_attempt_id .as_ref() diff --git a/crates/router/src/core/payments/operations/payment_get_intent.rs b/crates/router/src/core/payments/operations/payment_get_intent.rs index 3cc26e7b67f..0041209eb0f 100644 --- a/crates/router/src/core/payments/operations/payment_get_intent.rs +++ b/crates/router/src/core/payments/operations/payment_get_intent.rs @@ -10,6 +10,7 @@ use crate::{ core::{ errors::{self, RouterResult}, payments::{self, helpers, operations}, + utils::ValidatePlatformMerchant, }, db::errors::StorageErrorExt, routes::{app::ReqState, SessionState}, @@ -89,7 +90,7 @@ impl GetTracker, Payme _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); @@ -99,6 +100,9 @@ impl GetTracker, Payme .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + let payment_data = payments::PaymentIntentData { flow: PhantomData, payment_intent, diff --git a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs index 0e189583d0a..9f21dbc9981 100644 --- a/crates/router/src/core/payments/operations/payment_post_session_tokens.rs +++ b/crates/router/src/core/payments/operations/payment_post_session_tokens.rs @@ -73,6 +73,8 @@ impl GetTracker, api::PaymentsPostSess .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + helpers::authenticate_client_secret(Some(request.client_secret.peek()), &payment_intent)?; let mut payment_attempt = db diff --git a/crates/router/src/core/payments/operations/payment_reject.rs b/crates/router/src/core/payments/operations/payment_reject.rs index e5321b1f984..1591b1d6a47 100644 --- a/crates/router/src/core/payments/operations/payment_reject.rs +++ b/crates/router/src/core/payments/operations/payment_reject.rs @@ -11,6 +11,7 @@ use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, payments::{helpers, operations, PaymentAddress, PaymentData}, + utils::ValidatePlatformMerchant, }, events::audit_events::{AuditEvent, AuditEventType}, routes::{app::ReqState, SessionState}, @@ -43,7 +44,7 @@ impl GetTracker, PaymentsCancelRequest key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; @@ -66,6 +67,9 @@ impl GetTracker, PaymentsCancelRequest .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[ diff --git a/crates/router/src/core/payments/operations/payment_session.rs b/crates/router/src/core/payments/operations/payment_session.rs index 0e94fbe09c6..be4f804a8a7 100644 --- a/crates/router/src/core/payments/operations/payment_session.rs +++ b/crates/router/src/core/payments/operations/payment_session.rs @@ -68,6 +68,8 @@ impl GetTracker, api::PaymentsSessionR .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[ diff --git a/crates/router/src/core/payments/operations/payment_session_intent.rs b/crates/router/src/core/payments/operations/payment_session_intent.rs index ec59ba3473e..9bca88c6d04 100644 --- a/crates/router/src/core/payments/operations/payment_session_intent.rs +++ b/crates/router/src/core/payments/operations/payment_session_intent.rs @@ -10,7 +10,7 @@ use super::{BoxedOperation, Domain, GetTracker, Operation, ValidateRequest}; use crate::{ core::{ errors::{self, RouterResult, StorageErrorExt}, - payments::{self, operations, operations::ValidateStatusForOperation}, + payments::{self, helpers, operations, operations::ValidateStatusForOperation}, }, routes::SessionState, types::{api, domain, storage::enums}, @@ -112,6 +112,8 @@ impl GetTracker, Payme .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + self.validate_status_for_operation(payment_intent.status)?; let client_secret = header_payload diff --git a/crates/router/src/core/payments/operations/payment_start.rs b/crates/router/src/core/payments/operations/payment_start.rs index 1da9a1a2264..299d7237807 100644 --- a/crates/router/src/core/payments/operations/payment_start.rs +++ b/crates/router/src/core/payments/operations/payment_start.rs @@ -68,6 +68,8 @@ impl GetTracker, api::PaymentsStartReq .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once Merchant ID auth is solved + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[ diff --git a/crates/router/src/core/payments/operations/payment_status.rs b/crates/router/src/core/payments/operations/payment_status.rs index 44f0c9172f9..c458f95ad97 100644 --- a/crates/router/src/core/payments/operations/payment_status.rs +++ b/crates/router/src/core/payments/operations/payment_status.rs @@ -213,7 +213,7 @@ impl GetTracker, api::PaymentsRetrieve key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse<'a, F, api::PaymentsRetrieveRequest, PaymentData>, > { @@ -225,6 +225,7 @@ impl GetTracker, api::PaymentsRetrieve request, self, merchant_account.storage_scheme, + platform_merchant_account, ) .await } @@ -252,6 +253,7 @@ async fn get_tracker_for_sync< any(feature = "v2", feature = "v1"), not(feature = "payment_methods_v2") ))] +#[allow(clippy::too_many_arguments)] async fn get_tracker_for_sync< 'a, F: Send + Clone, @@ -264,6 +266,7 @@ async fn get_tracker_for_sync< request: &api::PaymentsRetrieveRequest, operation: Op, storage_scheme: enums::MerchantStorageScheme, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let (payment_intent, mut payment_attempt, currency, amount); @@ -274,6 +277,7 @@ async fn get_tracker_for_sync< merchant_account.get_id(), key_store, storage_scheme, + platform_merchant_account, ) .await?; @@ -579,6 +583,7 @@ pub async fn get_payment_intent_payment_attempt( merchant_id: &common_utils::id_type::MerchantId, key_store: &domain::MerchantKeyStore, storage_scheme: enums::MerchantStorageScheme, + _platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult<(storage::PaymentIntent, storage::PaymentAttempt)> { let key_manager_state: KeyManagerState = state.into(); let db = &*state.store; @@ -662,4 +667,6 @@ pub async fn get_payment_intent_payment_attempt( get_pi_pa() .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound) + + // TODO (#7195): Add platform merchant account validation once client_secret auth is solved } diff --git a/crates/router/src/core/payments/operations/payment_update.rs b/crates/router/src/core/payments/operations/payment_update.rs index 0e60407aa7c..ac37513f2b9 100644 --- a/crates/router/src/core/payments/operations/payment_update.rs +++ b/crates/router/src/core/payments/operations/payment_update.rs @@ -81,6 +81,8 @@ impl GetTracker, api::PaymentsRequest> .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + if let Some(order_details) = &request.order_details { helpers::validate_order_details_amount( order_details.to_owned(), diff --git a/crates/router/src/core/payments/operations/payment_update_intent.rs b/crates/router/src/core/payments/operations/payment_update_intent.rs index 2931c11b070..af6b7d57838 100644 --- a/crates/router/src/core/payments/operations/payment_update_intent.rs +++ b/crates/router/src/core/payments/operations/payment_update_intent.rs @@ -21,9 +21,10 @@ use crate::{ core::{ errors::{self, RouterResult}, payments::{ - self, + self, helpers, operations::{self, ValidateStatusForOperation}, }, + utils::ValidatePlatformMerchant, }, db::errors::StorageErrorExt, routes::{app::ReqState, SessionState}, @@ -135,7 +136,7 @@ impl GetTracker, PaymentsUpda _profile: &domain::Profile, key_store: &domain::MerchantKeyStore, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult>> { let db = &*state.store; let key_manager_state = &state.into(); @@ -145,6 +146,9 @@ impl GetTracker, PaymentsUpda .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + self.validate_status_for_operation(payment_intent.status)?; let PaymentsUpdateIntentRequest { diff --git a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs index 2c816ad39d1..95c50f2f17e 100644 --- a/crates/router/src/core/payments/operations/payments_incremental_authorization.rs +++ b/crates/router/src/core/payments/operations/payments_incremental_authorization.rs @@ -15,6 +15,7 @@ use crate::{ self, helpers, operations, CustomerDetails, IncrementalAuthorizationDetails, PaymentAddress, }, + utils::ValidatePlatformMerchant, }, routes::{app::ReqState, SessionState}, services, @@ -48,7 +49,7 @@ impl key_store: &domain::MerchantKeyStore, _auth_flow: services::AuthFlow, _header_payload: &hyperswitch_domain_models::payments::HeaderPayload, - _platform_merchant_account: Option<&domain::MerchantAccount>, + platform_merchant_account: Option<&domain::MerchantAccount>, ) -> RouterResult< operations::GetTrackerResponse< 'a, @@ -77,6 +78,9 @@ impl .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + payment_intent + .validate_platform_merchant(platform_merchant_account.map(|ma| ma.get_id()))?; + helpers::validate_payment_status_against_allowed_statuses( payment_intent.status, &[enums::IntentStatus::RequiresCapture], diff --git a/crates/router/src/core/payments/operations/tax_calculation.rs b/crates/router/src/core/payments/operations/tax_calculation.rs index 5b9c90f3add..bcda2af8768 100644 --- a/crates/router/src/core/payments/operations/tax_calculation.rs +++ b/crates/router/src/core/payments/operations/tax_calculation.rs @@ -79,6 +79,8 @@ impl .await .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + // TODO (#7195): Add platform merchant account validation once publishable key auth is solved + helpers::validate_payment_status_against_not_allowed_statuses( payment_intent.status, &[ diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index cd2a9ed220b..49075e7f548 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -1760,3 +1760,36 @@ pub(crate) fn validate_profile_id_from_auth_layer Ok(()), } } + +pub(crate) trait ValidatePlatformMerchant { + fn get_platform_merchant_id(&self) -> Option<&common_utils::id_type::MerchantId>; + + fn validate_platform_merchant( + &self, + auth_platform_merchant_id: Option<&common_utils::id_type::MerchantId>, + ) -> CustomResult<(), errors::ApiErrorResponse> { + let data_platform_merchant_id = self.get_platform_merchant_id(); + match (data_platform_merchant_id, auth_platform_merchant_id) { + (Some(data_platform_merchant_id), Some(auth_platform_merchant_id)) => { + common_utils::fp_utils::when( + data_platform_merchant_id != auth_platform_merchant_id, + || { + Err(report!(errors::ApiErrorResponse::PaymentNotFound)).attach_printable(format!( + "Data platform merchant id: {data_platform_merchant_id:?} does not match with auth platform merchant id: {auth_platform_merchant_id:?}")) + }, + ) + } + (Some(_), None) | (None, Some(_)) => { + Err(report!(errors::ApiErrorResponse::InvalidPlatformOperation)) + .attach_printable("Platform merchant id is missing in either data or auth") + } + (None, None) => Ok(()), + } + } +} + +impl ValidatePlatformMerchant for storage::PaymentIntent { + fn get_platform_merchant_id(&self) -> Option<&common_utils::id_type::MerchantId> { + self.platform_merchant_id.as_ref() + } +} diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 86f1874ab09..0b6246d4483 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -415,6 +415,7 @@ pub async fn list_payment_method_api( &req, payload, |state, auth: auth::AuthenticationData, req, _| { + // TODO (#7195): Fill platform_merchant_account in the client secret auth and pass it here. cards::list_payment_methods(state, auth.merchant_account, auth.key_store, req) }, &*auth, From 3c7cb9e59dc28bf79cf83793ae168491cfed717f Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Fri, 14 Feb 2025 14:49:19 +0530 Subject: [PATCH 095/133] feat(core): add card_discovery filter to payment list and payments Response (#7230) --- api-reference/openapi_spec.json | 25 +++++++++++++++++++ crates/api_models/src/payments.rs | 9 +++++++ crates/common_enums/src/enums.rs | 1 + .../src/query/payment_attempt.rs | 4 +++ .../src/payments/payment_attempt.rs | 1 + .../src/payments/payment_intent.rs | 5 ++++ crates/openapi/src/openapi.rs | 1 + crates/router/src/core/payments.rs | 2 ++ .../router/src/core/payments/transformers.rs | 2 ++ crates/router/src/db/kafka_store.rs | 2 ++ .../src/services/kafka/payment_attempt.rs | 4 +++ .../services/kafka/payment_attempt_event.rs | 4 +++ crates/router/tests/payments.rs | 2 ++ crates/router/tests/payments2.rs | 2 ++ .../src/mock_db/payment_attempt.rs | 1 + .../src/payments/payment_attempt.rs | 4 +++ .../src/payments/payment_intent.rs | 5 ++++ 17 files changed, 74 insertions(+) diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 61892d1b29e..e9b50395320 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9098,6 +9098,15 @@ }, "additionalProperties": false }, + "CardDiscovery": { + "type": "string", + "description": "Indicates the method by which a card is discovered during a payment", + "enum": [ + "manual", + "saved_card", + "click_to_pay" + ] + }, "CardNetwork": { "type": "string", "description": "Indicates the card network.", @@ -19220,6 +19229,14 @@ "type": "string", "description": "Connector Identifier for the payment method", "nullable": true + }, + "card_discovery": { + "allOf": [ + { + "$ref": "#/components/schemas/CardDiscovery" + } + ], + "nullable": true } } }, @@ -20463,6 +20480,14 @@ "type": "string", "description": "Connector Identifier for the payment method", "nullable": true + }, + "card_discovery": { + "allOf": [ + { + "$ref": "#/components/schemas/CardDiscovery" + } + ], + "nullable": true } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index bec25529af9..47f43f982f5 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4854,6 +4854,10 @@ pub struct PaymentsResponse { /// Connector Identifier for the payment method pub connector_mandate_id: Option, + + /// Method through which card was discovered + #[schema(value_type = Option, example = "manual")] + pub card_discovery: Option, } // Serialize is implemented because, this will be serialized in the api events. @@ -5552,6 +5556,8 @@ pub struct PaymentListFilterConstraints { pub card_network: Option>, /// The identifier for merchant order reference id pub merchant_order_reference_id: Option, + /// Indicates the method by which a card is discovered during a payment + pub card_discovery: Option>, } impl PaymentListFilterConstraints { @@ -5562,6 +5568,7 @@ impl PaymentListFilterConstraints { && self.authentication_type.is_none() && self.merchant_connector_id.is_none() && self.card_network.is_none() + && self.card_discovery.is_none() } } @@ -5595,6 +5602,8 @@ pub struct PaymentListFiltersV2 { pub authentication_type: Vec, /// The list of available card networks pub card_network: Vec, + /// The list of available Card discovery methods + pub card_discovery: Vec, } #[derive(Clone, Debug, serde::Serialize)] diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 2a5219f4dea..99442d3080c 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -194,6 +194,7 @@ impl AttemptStatus { serde::Serialize, strum::Display, strum::EnumString, + strum::EnumIter, ToSchema, )] #[router_derive::diesel_enum(storage_type = "db_enum")] diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 749ed41bcb4..60393211968 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -424,6 +424,7 @@ impl PaymentAttempt { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, ) -> StorageResult { let mut filter = ::table() .count() @@ -450,6 +451,9 @@ impl PaymentAttempt { if let Some(card_network) = card_network { filter = filter.filter(dsl::card_network.eq_any(card_network)) } + if let Some(card_discovery) = card_discovery { + filter = filter.filter(dsl::card_discovery.eq_any(card_discovery)) + } router_env::logger::debug!(query = %debug_query::(&filter).to_string()); diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index ee8b24c27b3..af462443cdf 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -196,6 +196,7 @@ pub trait PaymentAttemptInterface { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index cd16fbcb253..e20b0860161 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1113,6 +1113,7 @@ pub struct PaymentIntentListParams { pub limit: Option, pub order: api_models::payments::Order, pub card_network: Option>, + pub card_discovery: Option>, pub merchant_order_reference_id: Option, } @@ -1148,6 +1149,7 @@ impl From for PaymentIntentFetchCo limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), order: Default::default(), card_network: None, + card_discovery: None, merchant_order_reference_id: None, })) } @@ -1174,6 +1176,7 @@ impl From for PaymentIntentFetchConstraints { limit: None, order: Default::default(), card_network: None, + card_discovery: None, merchant_order_reference_id: None, })) } @@ -1198,6 +1201,7 @@ impl From for PaymentIntentF merchant_connector_id, order, card_network, + card_discovery, merchant_order_reference_id, } = value; if let Some(payment_intent_id) = payment_id { @@ -1222,6 +1226,7 @@ impl From for PaymentIntentF limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V2)), order, card_network, + card_discovery, merchant_order_reference_id, })) } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index 4a4e7f5c8e9..eb5edb08fab 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -312,6 +312,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PaymentMethodStatus, api_models::enums::UIWidgetFormLayout, api_models::enums::PaymentConnectorCategory, + api_models::enums::CardDiscovery, api_models::enums::FeatureStatus, api_models::admin::MerchantConnectorCreate, api_models::admin::AdditionalMerchantData, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index fd2950f5069..6bee4263b9b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5182,6 +5182,7 @@ pub async fn apply_filters_on_payments( constraints.authentication_type, constraints.merchant_connector_id, constraints.card_network, + constraints.card_discovery, merchant.storage_scheme, ) .await @@ -5320,6 +5321,7 @@ pub async fn get_payment_filters( payment_method: payment_method_types_map, authentication_type: enums::AuthenticationType::iter().collect(), card_network: enums::CardNetwork::iter().collect(), + card_discovery: enums::CardDiscovery::iter().collect(), }, )) } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 3075c9f26d7..81393e792b0 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2496,6 +2496,7 @@ where order_tax_amount, connector_mandate_id, shipping_cost: payment_intent.shipping_cost, + card_discovery: payment_attempt.card_discovery, }; services::ApplicationResponse::JsonWithHeaders((payments_response, headers)) @@ -2752,6 +2753,7 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay order_tax_amount: None, connector_mandate_id:None, shipping_cost: None, + card_discovery: pa.card_discovery } } } diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 2dc7191863a..5d961317a1c 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1715,6 +1715,7 @@ impl PaymentAttemptInterface for KafkaStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.diesel_store @@ -1727,6 +1728,7 @@ impl PaymentAttemptInterface for KafkaStore { authentication_type, merchant_connector_id, card_network, + card_discovery, storage_scheme, ) .await diff --git a/crates/router/src/services/kafka/payment_attempt.rs b/crates/router/src/services/kafka/payment_attempt.rs index adfff7450bc..6232bbcbf07 100644 --- a/crates/router/src/services/kafka/payment_attempt.rs +++ b/crates/router/src/services/kafka/payment_attempt.rs @@ -57,6 +57,7 @@ pub struct KafkaPaymentAttempt<'a> { pub profile_id: &'a id_type::ProfileId, pub organization_id: &'a id_type::OrganizationId, pub card_network: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -115,6 +116,9 @@ impl<'a> KafkaPaymentAttempt<'a> { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()), + card_discovery: attempt + .card_discovery + .map(|discovery| discovery.to_string()), } } } diff --git a/crates/router/src/services/kafka/payment_attempt_event.rs b/crates/router/src/services/kafka/payment_attempt_event.rs index ec67dc75953..f3fcd701846 100644 --- a/crates/router/src/services/kafka/payment_attempt_event.rs +++ b/crates/router/src/services/kafka/payment_attempt_event.rs @@ -58,6 +58,7 @@ pub struct KafkaPaymentAttemptEvent<'a> { pub profile_id: &'a id_type::ProfileId, pub organization_id: &'a id_type::OrganizationId, pub card_network: Option, + pub card_discovery: Option, } #[cfg(feature = "v1")] @@ -116,6 +117,9 @@ impl<'a> KafkaPaymentAttemptEvent<'a> { .and_then(|card| card.get("card_network")) .and_then(|network| network.as_str()) .map(|network| network.to_string()), + card_discovery: attempt + .card_discovery + .map(|discovery| discovery.to_string()), } } } diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index e1fe40b4206..82871fcfe25 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -451,6 +451,7 @@ async fn payments_create_core() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }; let expected_response = services::ApplicationResponse::JsonWithHeaders((expected_response, vec![])); @@ -715,6 +716,7 @@ async fn payments_create_core_adyen_no_redirect() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }, vec![], )); diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 49d2e12b819..63df5a2decf 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -212,6 +212,7 @@ async fn payments_create_core() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }; let expected_response = @@ -485,6 +486,7 @@ async fn payments_create_core_adyen_no_redirect() { order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, + card_discovery: None, }, vec![], )); diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index 7bf4266bf5b..c9d250d306b 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -53,6 +53,7 @@ impl PaymentAttemptInterface for MockDb { _authentication_type: Option>, _merchanat_connector_id: Option>, _card_network: Option>, + _card_discovery: Option>, _storage_scheme: storage_enums::MerchantStorageScheme, ) -> CustomResult { Err(StorageError::MockDbError)? diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 5b5f50f5aa4..7fb1d3ab80d 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -474,6 +474,7 @@ impl PaymentAttemptInterface for RouterStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, _storage_scheme: MerchantStorageScheme, ) -> CustomResult { let conn = self @@ -498,6 +499,7 @@ impl PaymentAttemptInterface for RouterStore { authentication_type, merchant_connector_id, card_network, + card_discovery, ) .await .map_err(|er| { @@ -1404,6 +1406,7 @@ impl PaymentAttemptInterface for KVRouterStore { authentication_type: Option>, merchant_connector_id: Option>, card_network: Option>, + card_discovery: Option>, storage_scheme: MerchantStorageScheme, ) -> CustomResult { self.router_store @@ -1416,6 +1419,7 @@ impl PaymentAttemptInterface for KVRouterStore { authentication_type, merchant_connector_id, card_network, + card_discovery, storage_scheme, ) .await diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index 5290c2bfca4..a515898006b 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -1044,6 +1044,11 @@ impl PaymentIntentInterface for crate::RouterStore { if let Some(card_network) = ¶ms.card_network { query = query.filter(pa_dsl::card_network.eq_any(card_network.clone())); } + + if let Some(card_discovery) = ¶ms.card_discovery { + query = query.filter(pa_dsl::card_discovery.eq_any(card_discovery.clone())); + } + query } }; From 17f9e6ee9e99366fa0236a3f4266483d1d8dfa22 Mon Sep 17 00:00:00 2001 From: Sanchith Hegde <22217505+SanchithHegde@users.noreply.github.com> Date: Fri, 14 Feb 2025 14:58:54 +0530 Subject: [PATCH 096/133] refactor(payments_v2): create customer at connector end and populate connector customer ID (#7246) --- crates/diesel_models/src/customers.rs | 42 +++++-- .../src/business_profile.rs | 2 +- .../hyperswitch_domain_models/src/customer.rs | 116 +++++++++++------- crates/router/src/core/customers.rs | 31 ++--- crates/router/src/core/payments.rs | 108 ++++++++++++++-- crates/router/src/core/payments/customers.rs | 78 ++++++++---- .../operations/payment_confirm_intent.rs | 19 +++ .../router/src/core/payments/transformers.rs | 16 ++- crates/router/src/core/payouts.rs | 14 ++- crates/router/src/core/payouts/helpers.rs | 42 +++++-- crates/router/src/types/storage/customers.rs | 2 + 11 files changed, 356 insertions(+), 114 deletions(-) diff --git a/crates/diesel_models/src/customers.rs b/crates/diesel_models/src/customers.rs index dc39059c135..b0d4535ddb7 100644 --- a/crates/diesel_models/src/customers.rs +++ b/crates/diesel_models/src/customers.rs @@ -64,13 +64,7 @@ impl From for Customer { #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive( - Clone, - Debug, - PartialEq, - Insertable, - router_derive::DebugAsDisplay, - serde::Deserialize, - serde::Serialize, + Clone, Debug, Insertable, router_derive::DebugAsDisplay, serde::Deserialize, serde::Serialize, )] #[diesel(table_name = customers, primary_key(id))] pub struct CustomerNew { @@ -82,7 +76,7 @@ pub struct CustomerNew { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -164,7 +158,7 @@ pub struct Customer { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -242,7 +236,7 @@ pub struct CustomerUpdateInternal { pub phone_country_code: Option, pub metadata: Option, pub modified_at: PrimitiveDateTime, - pub connector_customer: Option, + pub connector_customer: Option, pub default_payment_method_id: Option>, pub updated_by: Option, pub default_billing_address: Option, @@ -289,3 +283,31 @@ impl CustomerUpdateInternal { } } } + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize, diesel::AsExpression)] +#[diesel(sql_type = diesel::sql_types::Jsonb)] +#[serde(transparent)] +pub struct ConnectorCustomerMap( + std::collections::HashMap, +); + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +common_utils::impl_to_sql_from_sql_json!(ConnectorCustomerMap); + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +impl std::ops::Deref for ConnectorCustomerMap { + type Target = + std::collections::HashMap; + + fn deref(&self) -> &Self::Target { + &self.0 + } +} + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +impl std::ops::DerefMut for ConnectorCustomerMap { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 + } +} diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index 3e49a596d88..b2385efb6d2 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -985,7 +985,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, - is_click_to_pay_enabled: None, + is_click_to_pay_enabled, authentication_product_ids, three_ds_decision_manager_config, } diff --git a/crates/hyperswitch_domain_models/src/customer.rs b/crates/hyperswitch_domain_models/src/customer.rs index 58103c08faa..1497c9e3622 100644 --- a/crates/hyperswitch_domain_models/src/customer.rs +++ b/crates/hyperswitch_domain_models/src/customer.rs @@ -56,7 +56,7 @@ pub struct Customer { pub description: Option, pub created_at: PrimitiveDateTime, pub metadata: Option, - pub connector_customer: Option, + pub connector_customer: Option, pub modified_at: PrimitiveDateTime, pub default_payment_method_id: Option, pub updated_by: Option, @@ -80,6 +80,31 @@ impl Customer { pub fn get_id(&self) -> &id_type::GlobalCustomerId { &self.id } + + /// Get the connector customer ID for the specified connector label, if present + #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] + pub fn get_connector_customer_id(&self, connector_label: &str) -> Option<&str> { + use masking::PeekInterface; + + self.connector_customer + .as_ref() + .and_then(|connector_customer_value| { + connector_customer_value.peek().get(connector_label) + }) + .and_then(|connector_customer| connector_customer.as_str()) + } + + /// Get the connector customer ID for the specified merchant connector account ID, if present + #[cfg(all(feature = "v2", feature = "customer_v2"))] + pub fn get_connector_customer_id( + &self, + merchant_connector_id: &id_type::MerchantConnectorAccountId, + ) -> Option<&str> { + self.connector_customer + .as_ref() + .and_then(|connector_customer_map| connector_customer_map.get(merchant_connector_id)) + .map(|connector_customer_id| connector_customer_id.as_str()) + } } #[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] @@ -300,24 +325,28 @@ impl super::behaviour::Conversion for Customer { } } +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[derive(Clone, Debug)] +pub struct CustomerGeneralUpdate { + pub name: crypto::OptionalEncryptableName, + pub email: Box, + pub phone: Box, + pub description: Option, + pub phone_country_code: Option, + pub metadata: Option, + pub connector_customer: Box>, + pub default_billing_address: Option, + pub default_shipping_address: Option, + pub default_payment_method_id: Option>, + pub status: Option, +} + #[cfg(all(feature = "v2", feature = "customer_v2"))] #[derive(Clone, Debug)] pub enum CustomerUpdate { - Update { - name: crypto::OptionalEncryptableName, - email: Box, - phone: Box, - description: Option, - phone_country_code: Option, - metadata: Option, - connector_customer: Box>, - default_billing_address: Option, - default_shipping_address: Option, - default_payment_method_id: Option>, - status: Option, - }, + Update(Box), ConnectorCustomer { - connector_customer: Option, + connector_customer: Option, }, UpdateDefaultPaymentMethod { default_payment_method_id: Option>, @@ -328,33 +357,36 @@ pub enum CustomerUpdate { impl From for CustomerUpdateInternal { fn from(customer_update: CustomerUpdate) -> Self { match customer_update { - CustomerUpdate::Update { - name, - email, - phone, - description, - phone_country_code, - metadata, - connector_customer, - default_billing_address, - default_shipping_address, - default_payment_method_id, - status, - } => Self { - name: name.map(Encryption::from), - email: email.map(Encryption::from), - phone: phone.map(Encryption::from), - description, - phone_country_code, - metadata, - connector_customer: *connector_customer, - modified_at: date_time::now(), - default_billing_address, - default_shipping_address, - default_payment_method_id, - updated_by: None, - status, - }, + CustomerUpdate::Update(update) => { + let CustomerGeneralUpdate { + name, + email, + phone, + description, + phone_country_code, + metadata, + connector_customer, + default_billing_address, + default_shipping_address, + default_payment_method_id, + status, + } = *update; + Self { + name: name.map(Encryption::from), + email: email.map(Encryption::from), + phone: phone.map(Encryption::from), + description, + phone_country_code, + metadata, + connector_customer: *connector_customer, + modified_at: date_time::now(), + default_billing_address, + default_shipping_address, + default_payment_method_id, + updated_by: None, + status, + } + } CustomerUpdate::ConnectorCustomer { connector_customer } => Self { connector_customer, name: None, diff --git a/crates/router/src/core/customers.rs b/crates/router/src/core/customers.rs index dabc9d37086..353c980c769 100644 --- a/crates/router/src/core/customers.rs +++ b/crates/router/src/core/customers.rs @@ -679,19 +679,20 @@ impl CustomerDeleteBridge for id_type::GlobalCustomerId { redacted_encrypted_value.clone().into_encrypted(), ); - let updated_customer = storage::CustomerUpdate::Update { - name: Some(redacted_encrypted_value.clone()), - email: Box::new(Some(redacted_encrypted_email)), - phone: Box::new(Some(redacted_encrypted_value.clone())), - description: Some(Description::from_str_unchecked(REDACTED)), - phone_country_code: Some(REDACTED.to_string()), - metadata: None, - connector_customer: Box::new(None), - default_billing_address: None, - default_shipping_address: None, - default_payment_method_id: None, - status: Some(common_enums::DeleteStatus::Redacted), - }; + let updated_customer = + storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate { + name: Some(redacted_encrypted_value.clone()), + email: Box::new(Some(redacted_encrypted_email)), + phone: Box::new(Some(redacted_encrypted_value.clone())), + description: Some(Description::from_str_unchecked(REDACTED)), + phone_country_code: Some(REDACTED.to_string()), + metadata: None, + connector_customer: Box::new(None), + default_billing_address: None, + default_shipping_address: None, + default_payment_method_id: None, + status: Some(common_enums::DeleteStatus::Redacted), + })); db.update_customer_by_global_id( key_manager_state, @@ -1338,7 +1339,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { &domain_customer.id, domain_customer.to_owned(), merchant_account.get_id(), - storage::CustomerUpdate::Update { + storage::CustomerUpdate::Update(Box::new(storage::CustomerGeneralUpdate { name: encryptable_customer.name, email: Box::new(encryptable_customer.email.map(|email| { let encryptable: Encryptable> = @@ -1357,7 +1358,7 @@ impl CustomerUpdateBridge for customers::CustomerUpdateRequest { default_shipping_address: encrypted_customer_shipping_address.map(Into::into), default_payment_method_id: Some(self.default_payment_method_id.clone()), status: None, - }, + })), key_store, merchant_account.storage_scheme, ) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 6bee4263b9b..57e72a42941 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -3016,6 +3016,16 @@ where id: merchant_connector_id.get_string_repr().to_owned(), })?; + let updated_customer = call_create_connector_customer_if_required( + state, + customer, + merchant_account, + key_store, + &merchant_connector_account, + payment_data, + ) + .await?; + let mut router_data = payment_data .construct_router_data( state, @@ -3068,8 +3078,7 @@ where payment_data.clone(), customer.clone(), merchant_account.storage_scheme, - // TODO: update the customer with connector customer id - None, + updated_customer, key_store, frm_suggestion, header_payload.clone(), @@ -3894,7 +3903,6 @@ where merchant_connector_account.get_mca_id(), )?; - #[cfg(feature = "v1")] let label = { let connector_label = core_utils::get_connector_label( payment_data.get_payment_intent().business_country, @@ -3925,15 +3933,6 @@ where } }; - #[cfg(feature = "v2")] - let label = { - merchant_connector_account - .get_mca_id() - .get_required_value("merchant_connector_account_id")? - .get_string_repr() - .to_owned() - }; - let (should_call_connector, existing_connector_customer_id) = customers::should_call_connector_create_customer( state, &connector, customer, &label, @@ -3961,7 +3960,90 @@ where let customer_update = customers::update_connector_customer_in_customers( &label, customer.as_ref(), - &connector_customer_id, + connector_customer_id.clone(), + ) + .await; + + payment_data.set_connector_customer_id(connector_customer_id); + Ok(customer_update) + } else { + // Customer already created in previous calls use the same value, no need to update + payment_data.set_connector_customer_id( + existing_connector_customer_id.map(ToOwned::to_owned), + ); + Ok(None) + } + } + None => Ok(None), + } +} + +#[cfg(feature = "v2")] +pub async fn call_create_connector_customer_if_required( + state: &SessionState, + customer: &Option, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + merchant_connector_account: &domain::MerchantConnectorAccount, + payment_data: &mut D, +) -> RouterResult> +where + F: Send + Clone + Sync, + Req: Send + Sync, + + // To create connector flow specific interface data + D: OperationSessionGetters + OperationSessionSetters + Send + Sync + Clone, + D: ConstructFlowSpecificData, + RouterData: Feature + Send, + + // To construct connector flow specific api + dyn api::Connector: + services::api::ConnectorIntegration, +{ + let connector_name = payment_data.get_payment_attempt().connector.clone(); + + match connector_name { + Some(connector_name) => { + let connector = api::ConnectorData::get_connector_by_name( + &state.conf.connectors, + &connector_name, + api::GetToken::Connector, + Some(merchant_connector_account.get_id()), + )?; + + let merchant_connector_id = merchant_connector_account.get_id(); + + let (should_call_connector, existing_connector_customer_id) = + customers::should_call_connector_create_customer( + state, + &connector, + customer, + &merchant_connector_id, + ); + + if should_call_connector { + // Create customer at connector and update the customer table to store this data + let router_data = payment_data + .construct_router_data( + state, + connector.connector.id(), + merchant_account, + key_store, + customer, + merchant_connector_account, + None, + None, + ) + .await?; + + let connector_customer_id = router_data + .create_connector_customer(state, &connector) + .await?; + + let customer_update = customers::update_connector_customer_in_customers( + merchant_connector_id, + customer.as_ref(), + connector_customer_id.clone(), ) .await; diff --git a/crates/router/src/core/payments/customers.rs b/crates/router/src/core/payments/customers.rs index b11216c423d..83f01349787 100644 --- a/crates/router/src/core/payments/customers.rs +++ b/crates/router/src/core/payments/customers.rs @@ -1,5 +1,5 @@ use common_utils::pii; -use masking::{ExposeOptionInterface, PeekInterface}; +use masking::ExposeOptionInterface; use router_env::{instrument, tracing}; use crate::{ @@ -73,22 +73,37 @@ pub async fn create_connector_customer( Ok(connector_customer_id) } -pub fn get_connector_customer_details_if_present<'a>( - customer: &'a domain::Customer, - connector_name: &str, -) -> Option<&'a str> { - customer +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] +pub fn should_call_connector_create_customer<'a>( + state: &SessionState, + connector: &api::ConnectorData, + customer: &'a Option, + connector_label: &str, +) -> (bool, Option<&'a str>) { + // Check if create customer is required for the connector + let connector_needs_customer = state + .conf .connector_customer - .as_ref() - .and_then(|connector_customer_value| connector_customer_value.peek().get(connector_name)) - .and_then(|connector_customer| connector_customer.as_str()) + .connector_list + .contains(&connector.connector_name); + + if connector_needs_customer { + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(connector_label)); + let should_call_connector = connector_customer_details.is_none(); + (should_call_connector, connector_customer_details) + } else { + (false, None) + } } +#[cfg(all(feature = "v2", feature = "customer_v2"))] pub fn should_call_connector_create_customer<'a>( state: &SessionState, connector: &api::ConnectorData, customer: &'a Option, - connector_label: &str, + merchant_connector_id: &common_utils::id_type::MerchantConnectorAccountId, ) -> (bool, Option<&'a str>) { // Check if create customer is required for the connector let connector_needs_customer = state @@ -98,9 +113,9 @@ pub fn should_call_connector_create_customer<'a>( .contains(&connector.connector_name); if connector_needs_customer { - let connector_customer_details = customer.as_ref().and_then(|customer| { - get_connector_customer_details_if_present(customer, connector_label) - }); + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(merchant_connector_id)); let should_call_connector = connector_customer_details.is_none(); (should_call_connector, connector_customer_details) } else { @@ -108,25 +123,23 @@ pub fn should_call_connector_create_customer<'a>( } } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] #[instrument] pub async fn update_connector_customer_in_customers( connector_label: &str, customer: Option<&domain::Customer>, - connector_customer_id: &Option, + connector_customer_id: Option, ) -> Option { - let connector_customer_map = customer + let mut connector_customer_map = customer .and_then(|customer| customer.connector_customer.clone().expose_option()) .and_then(|connector_customer| connector_customer.as_object().cloned()) .unwrap_or_default(); - let updated_connector_customer_map = - connector_customer_id.as_ref().map(|connector_customer_id| { - let mut connector_customer_map = connector_customer_map; - let connector_customer_value = - serde_json::Value::String(connector_customer_id.to_string()); - connector_customer_map.insert(connector_label.to_string(), connector_customer_value); - connector_customer_map - }); + let updated_connector_customer_map = connector_customer_id.map(|connector_customer_id| { + let connector_customer_value = serde_json::Value::String(connector_customer_id); + connector_customer_map.insert(connector_label.to_string(), connector_customer_value); + connector_customer_map + }); updated_connector_customer_map .map(serde_json::Value::Object) @@ -136,3 +149,22 @@ pub async fn update_connector_customer_in_customers( }, ) } + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +#[instrument] +pub async fn update_connector_customer_in_customers( + merchant_connector_id: common_utils::id_type::MerchantConnectorAccountId, + customer: Option<&domain::Customer>, + connector_customer_id: Option, +) -> Option { + connector_customer_id.map(|connector_customer_id| { + let mut connector_customer_map = customer + .and_then(|customer| customer.connector_customer.clone()) + .unwrap_or_default(); + connector_customer_map.insert(merchant_connector_id, connector_customer_id); + + storage::CustomerUpdate::ConnectorCustomer { + connector_customer: Some(connector_customer_map), + } + }) +} diff --git a/crates/router/src/core/payments/operations/payment_confirm_intent.rs b/crates/router/src/core/payments/operations/payment_confirm_intent.rs index 2e4c0190adb..79ebc2d8a38 100644 --- a/crates/router/src/core/payments/operations/payment_confirm_intent.rs +++ b/crates/router/src/core/payments/operations/payment_confirm_intent.rs @@ -462,6 +462,25 @@ impl UpdateTracker, PaymentsConfirmInt payment_data.payment_attempt = updated_payment_attempt; + if let Some((customer, updated_customer)) = customer.zip(updated_customer) { + let customer_id = customer.get_id().clone(); + let customer_merchant_id = customer.merchant_id.clone(); + + let _updated_customer = db + .update_customer_by_global_id( + key_manager_state, + &customer_id, + customer, + &customer_merchant_id, + updated_customer, + key_store, + storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to update customer during `update_trackers`")?; + } + Ok((Box::new(self), payment_data)) } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 81393e792b0..7302831e509 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -211,6 +211,12 @@ pub async fn construct_payment_router_data_for_authorize<'a>( "Invalid global customer generated, not able to convert to reference id", )?; + let connector_customer_id = customer.as_ref().and_then(|customer| { + customer + .get_connector_customer_id(&merchant_connector_account.get_id()) + .map(String::from) + }); + let payment_method = payment_data.payment_attempt.payment_method_type; let router_base_url = &state.base_url; @@ -352,7 +358,7 @@ pub async fn construct_payment_router_data_for_authorize<'a>( reference_id: None, payment_method_status: None, payment_method_token: None, - connector_customer: None, + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id @@ -867,6 +873,12 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( "Invalid global customer generated, not able to convert to reference id", )?; + let connector_customer_id = customer.as_ref().and_then(|customer| { + customer + .get_connector_customer_id(&merchant_connector_account.get_id()) + .map(String::from) + }); + let payment_method = payment_data.payment_attempt.payment_method_type; let router_base_url = &state.base_url; @@ -994,7 +1006,7 @@ pub async fn construct_payment_router_data_for_setup_mandate<'a>( reference_id: None, payment_method_status: None, payment_method_token: None, - connector_customer: None, + connector_customer: connector_customer_id, recurring_mandate_payment_data: None, // TODO: This has to be generated as the reference id based on the connector configuration // Some connectros might not accept accept the global id. This has to be done when generating the reference id diff --git a/crates/router/src/core/payouts.rs b/crates/router/src/core/payouts.rs index 58d5e22b4b9..392cfd4382f 100644 --- a/crates/router/src/core/payouts.rs +++ b/crates/router/src/core/payouts.rs @@ -1203,6 +1203,7 @@ pub async fn complete_create_recipient( Ok(()) } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] pub async fn create_recipient( state: &SessionState, merchant_account: &domain::MerchantAccount, @@ -1262,7 +1263,7 @@ pub async fn create_recipient( customers::update_connector_customer_in_customers( &connector_label, Some(&customer), - &recipient_create_data.connector_payout_id.clone(), + recipient_create_data.connector_payout_id.clone(), ) .await { @@ -1402,6 +1403,17 @@ pub async fn create_recipient( Ok(()) } +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub async fn create_recipient( + state: &SessionState, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + connector_data: &api::ConnectorData, + payout_data: &mut PayoutData, +) -> RouterResult<()> { + todo!() +} + pub async fn complete_payout_eligibility( state: &SessionState, merchant_account: &domain::MerchantAccount, diff --git a/crates/router/src/core/payouts/helpers.rs b/crates/router/src/core/payouts/helpers.rs index 77cd9c266ff..7eb7925017e 100644 --- a/crates/router/src/core/payouts/helpers.rs +++ b/crates/router/src/core/payouts/helpers.rs @@ -29,10 +29,7 @@ use crate::{ transformers::{DataDuplicationCheck, StoreCardReq, StoreGenericReq, StoreLockerReq}, vault, }, - payments::{ - customers::get_connector_customer_details_if_present, helpers as payment_helpers, - routing, CustomerDetails, - }, + payments::{helpers as payment_helpers, routing, CustomerDetails}, routing::TransactionData, utils as core_utils, }, @@ -965,6 +962,7 @@ pub async fn get_default_payout_connector( )) } +#[cfg(all(any(feature = "v1", feature = "v2"), not(feature = "customer_v2")))] pub fn should_call_payout_connector_create_customer<'a>( state: &'a SessionState, connector: &'a api::ConnectorData, @@ -981,9 +979,39 @@ pub fn should_call_payout_connector_create_customer<'a>( .contains(&connector); if connector_needs_customer { - let connector_customer_details = customer.as_ref().and_then(|customer| { - get_connector_customer_details_if_present(customer, connector_label) - }); + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(connector_label)); + let should_call_connector = connector_customer_details.is_none(); + (should_call_connector, connector_customer_details) + } else { + (false, None) + } + } + _ => (false, None), + } +} + +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub fn should_call_payout_connector_create_customer<'a>( + state: &'a SessionState, + connector: &'a api::ConnectorData, + customer: &'a Option, + merchant_connector_id: &'a id_type::MerchantConnectorAccountId, +) -> (bool, Option<&'a str>) { + // Check if create customer is required for the connector + match enums::PayoutConnectors::try_from(connector.connector_name) { + Ok(connector) => { + let connector_needs_customer = state + .conf + .connector_customer + .payout_connector_list + .contains(&connector); + + if connector_needs_customer { + let connector_customer_details = customer + .as_ref() + .and_then(|customer| customer.get_connector_customer_id(merchant_connector_id)); let should_call_connector = connector_customer_details.is_none(); (should_call_connector, connector_customer_details) } else { diff --git a/crates/router/src/types/storage/customers.rs b/crates/router/src/types/storage/customers.rs index 8566d7365a4..507c219534d 100644 --- a/crates/router/src/types/storage/customers.rs +++ b/crates/router/src/types/storage/customers.rs @@ -1,3 +1,5 @@ pub use diesel_models::customers::{Customer, CustomerNew, CustomerUpdateInternal}; +#[cfg(all(feature = "v2", feature = "customer_v2"))] +pub use crate::types::domain::CustomerGeneralUpdate; pub use crate::types::domain::CustomerUpdate; From fbbbe3eb90c476e7986114824279a11237332d1f Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 14 Feb 2025 15:28:02 +0530 Subject: [PATCH 097/133] ci(nix): update om command invocation (#7259) --- .github/workflows/nix.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/nix.yaml b/.github/workflows/nix.yaml index cc68a1b2b2c..4009af20a1b 100644 --- a/.github/workflows/nix.yaml +++ b/.github/workflows/nix.yaml @@ -14,4 +14,4 @@ jobs: system: [x86_64-linux, aarch64-darwin] steps: - uses: actions/checkout@v4 - - run: om ci --extra-access-tokens ${{ secrets.GITHUB_TOKEN }} run --systems "${{ matrix.system }}" + - run: om ci run --extra-access-tokens ${{ secrets.GITHUB_TOKEN }} --systems "${{ matrix.system }}" From 2ee22cdf8aced4881c1aab70cd10797a4deb57ed Mon Sep 17 00:00:00 2001 From: CHALLA NISHANTH BABU <115225644+NISHANTH1221@users.noreply.github.com> Date: Fri, 14 Feb 2025 16:19:58 +0530 Subject: [PATCH 098/133] refactor(router): add revenue_recovery_metadata to payment intent in diesel and api model for v2 flow (#7176) Co-authored-by: Nishanth Challa Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 75 +++++++++++++++ crates/api_models/src/payments.rs | 53 ++++++++++- crates/common_enums/src/enums.rs | 9 ++ crates/diesel_models/src/types.rs | 48 +++++++++- crates/hyperswitch_domain_models/src/lib.rs | 100 ++++++++++++++++++++ crates/openapi/src/openapi_v2.rs | 3 + 6 files changed, 285 insertions(+), 3 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index cadae4fa45c..3a43b1bfca2 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -5793,6 +5793,23 @@ } } }, + "BillingConnectorPaymentDetails": { + "type": "object", + "required": [ + "payment_processor_token", + "connector_customer_id" + ], + "properties": { + "payment_processor_token": { + "type": "string", + "description": "Payment Processor Token to process the Revenue Recovery Payment" + }, + "connector_customer_id": { + "type": "string", + "description": "Billing Connector's Customer Id" + } + } + }, "BlikBankRedirectAdditionalData": { "type": "object", "properties": { @@ -9025,6 +9042,14 @@ } ], "nullable": true + }, + "payment_revenue_recovery_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentRevenueRecoveryMetadata" + } + ], + "nullable": true } } }, @@ -13167,6 +13192,13 @@ "bank_acquirer" ] }, + "PaymentConnectorTransmission": { + "type": "string", + "enum": [ + "ConnectorCallFailed", + "ConnectorCallSucceeded" + ] + }, "PaymentCreatePaymentLinkConfig": { "allOf": [ { @@ -14926,6 +14958,49 @@ } } }, + "PaymentRevenueRecoveryMetadata": { + "type": "object", + "required": [ + "total_retry_count", + "payment_connector_transmission", + "billing_connector_id", + "active_attempt_payment_connector_id", + "billing_connector_payment_details", + "payment_method_type", + "payment_method_subtype" + ], + "properties": { + "total_retry_count": { + "type": "integer", + "format": "int32", + "description": "Total number of billing connector + recovery retries for a payment intent.", + "example": "1", + "minimum": 0 + }, + "payment_connector_transmission": { + "$ref": "#/components/schemas/PaymentConnectorTransmission" + }, + "billing_connector_id": { + "type": "string", + "description": "Billing Connector Id to update the invoices", + "example": "mca_1234567890" + }, + "active_attempt_payment_connector_id": { + "type": "string", + "description": "Payment Connector Id to retry the payments", + "example": "mca_1234567890" + }, + "billing_connector_payment_details": { + "$ref": "#/components/schemas/BillingConnectorPaymentDetails" + }, + "payment_method_type": { + "$ref": "#/components/schemas/PaymentMethod" + }, + "payment_method_subtype": { + "$ref": "#/components/schemas/PaymentMethodType" + } + } + }, "PaymentType": { "type": "string", "description": "The type of the payment that differentiates between normal and various types of mandate payments. Use 'setup_mandate' in case of zero auth flow.", diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 47f43f982f5..fb033230654 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -5,6 +5,8 @@ use std::{ }; pub mod additional_info; use cards::CardNumber; +#[cfg(feature = "v2")] +use common_enums::enums::PaymentConnectorTransmission; use common_enums::ProductType; #[cfg(feature = "v2")] use common_utils::id_type::GlobalPaymentId; @@ -7111,12 +7113,28 @@ pub struct PaymentsStartRequest { } /// additional data that might be required by hyperswitch +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] +pub struct FeatureMetadata { + /// Redirection response coming in request as metadata field only for redirection scenarios + #[schema(value_type = Option)] + pub redirect_response: Option, + /// Additional tags to be used for global search + #[schema(value_type = Option>)] + pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, + /// revenue recovery data for payment intent + pub payment_revenue_recovery_metadata: Option, +} + +/// additional data that might be required by hyperswitch +#[cfg(feature = "v1")] #[derive(Debug, Clone, serde::Deserialize, serde::Serialize, ToSchema)] pub struct FeatureMetadata { /// Redirection response coming in request as metadata field only for redirection scenarios #[schema(value_type = Option)] pub redirect_response: Option, - // TODO: Convert this to hashedstrings to avoid PII sensitive data /// Additional tags to be used for global search #[schema(value_type = Option>)] pub search_tags: Option>>, @@ -7977,3 +7995,36 @@ mod billing_from_payment_method_data { assert!(billing_details.is_none()); } } + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +pub struct PaymentRevenueRecoveryMetadata { + /// Total number of billing connector + recovery retries for a payment intent. + #[schema(value_type = u16,example = "1")] + pub total_retry_count: u16, + /// Flag for the payment connector's call + pub payment_connector_transmission: PaymentConnectorTransmission, + /// Billing Connector Id to update the invoices + #[schema(value_type = String, example = "mca_1234567890")] + pub billing_connector_id: id_type::MerchantConnectorAccountId, + /// Payment Connector Id to retry the payments + #[schema(value_type = String, example = "mca_1234567890")] + pub active_attempt_payment_connector_id: id_type::MerchantConnectorAccountId, + /// Billing Connector Payment Details + #[schema(value_type = BillingConnectorPaymentDetails)] + pub billing_connector_payment_details: BillingConnectorPaymentDetails, + /// Payment Method Type + #[schema(example = "pay_later", value_type = PaymentMethod)] + pub payment_method_type: common_enums::PaymentMethod, + /// PaymentMethod Subtype + #[schema(example = "klarna", value_type = PaymentMethodType)] + pub payment_method_subtype: common_enums::PaymentMethodType, +} +#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)] +#[cfg(feature = "v2")] +pub struct BillingConnectorPaymentDetails { + /// Payment Processor Token to process the Revenue Recovery Payment + pub payment_processor_token: String, + /// Billing Connector's Customer Id + pub connector_customer_id: String, +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 99442d3080c..f7ea487e91e 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -3768,3 +3768,12 @@ pub enum AdyenSplitType { /// The value-added tax charged on the payment, booked to your platforms liable balance account. Vat, } + +#[derive(Clone, Copy, Debug, Eq, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)] +#[serde(rename = "snake_case")] +pub enum PaymentConnectorTransmission { + /// Failed to call the payment connector + ConnectorCallFailed, + /// Payment Connector call succeeded + ConnectorCallSucceeded, +} diff --git a/crates/diesel_models/src/types.rs b/crates/diesel_models/src/types.rs index 7806a7c6e1b..6bc8aee2893 100644 --- a/crates/diesel_models/src/types.rs +++ b/crates/diesel_models/src/types.rs @@ -1,10 +1,12 @@ +#[cfg(feature = "v2")] +use common_enums::{enums::PaymentConnectorTransmission, PaymentMethod, PaymentMethodType}; use common_utils::{hashing::HashedString, pii, types::MinorUnit}; use diesel::{ sql_types::{Json, Jsonb}, AsExpression, FromSqlRow, }; use masking::{Secret, WithType}; -use serde::{Deserialize, Serialize}; +use serde::{self, Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Jsonb)] pub struct OrderDetailsWithAmount { @@ -40,12 +42,26 @@ impl masking::SerializableSecret for OrderDetailsWithAmount {} common_utils::impl_to_sql_from_sql_json!(OrderDetailsWithAmount); +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] +#[diesel(sql_type = Json)] +pub struct FeatureMetadata { + /// Redirection response coming in request as metadata field only for redirection scenarios + pub redirect_response: Option, + /// Additional tags to be used for global search + pub search_tags: Option>>, + /// Recurring payment details required for apple pay Merchant Token + pub apple_pay_recurring_details: Option, + /// revenue recovery data for payment intent + pub payment_revenue_recovery_metadata: Option, +} + +#[cfg(feature = "v1")] #[derive(Debug, Clone, PartialEq, Deserialize, Serialize, FromSqlRow, AsExpression)] #[diesel(sql_type = Json)] pub struct FeatureMetadata { /// Redirection response coming in request as metadata field only for redirection scenarios pub redirect_response: Option, - // TODO: Convert this to hashedstrings to avoid PII sensitive data /// Additional tags to be used for global search pub search_tags: Option>>, /// Recurring payment details required for apple pay Merchant Token @@ -106,3 +122,31 @@ pub struct RedirectResponse { } impl masking::SerializableSecret for RedirectResponse {} common_utils::impl_to_sql_from_sql_json!(RedirectResponse); + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +pub struct PaymentRevenueRecoveryMetadata { + /// Total number of billing connector + recovery retries for a payment intent. + pub total_retry_count: u16, + /// Flag for the payment connector's call + pub payment_connector_transmission: PaymentConnectorTransmission, + /// Billing Connector Id to update the invoices + pub billing_connector_id: common_utils::id_type::MerchantConnectorAccountId, + /// Payment Connector Id to retry the payments + pub active_attempt_payment_connector_id: common_utils::id_type::MerchantConnectorAccountId, + /// Billing Connector Payment Details + pub billing_connector_payment_details: BillingConnectorPaymentDetails, + ///Payment Method Type + pub payment_method_type: PaymentMethod, + /// PaymentMethod Subtype + pub payment_method_subtype: PaymentMethodType, +} + +#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)] +#[cfg(feature = "v2")] +pub struct BillingConnectorPaymentDetails { + /// Payment Processor Token to process the Revenue Recovery Payment + pub payment_processor_token: String, + /// Billing Connector's Customer Id + pub connector_customer_id: String, +} diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index bd6dd2e5371..b2546e3a049 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -40,10 +40,17 @@ use api_models::payments::{ RecurringPaymentIntervalUnit as ApiRecurringPaymentIntervalUnit, RedirectResponse as ApiRedirectResponse, }; +#[cfg(feature = "v2")] +use api_models::payments::{ + BillingConnectorPaymentDetails as ApiBillingConnectorPaymentDetails, + PaymentRevenueRecoveryMetadata as ApiRevenueRecoveryMetadata, +}; use diesel_models::types::{ ApplePayRecurringDetails, ApplePayRegularBillingDetails, FeatureMetadata, OrderDetailsWithAmount, RecurringPaymentIntervalUnit, RedirectResponse, }; +#[cfg(feature = "v2")] +use diesel_models::types::{BillingConnectorPaymentDetails, PaymentRevenueRecoveryMetadata}; #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] pub enum RemoteStorageObject { @@ -78,18 +85,57 @@ pub trait ApiModelToDieselModelConvertor { fn convert_back(self) -> F; } +#[cfg(feature = "v1")] +impl ApiModelToDieselModelConvertor for FeatureMetadata { + fn convert_from(from: ApiFeatureMetadata) -> Self { + let ApiFeatureMetadata { + redirect_response, + search_tags, + apple_pay_recurring_details, + } = from; + + Self { + redirect_response: redirect_response.map(RedirectResponse::convert_from), + search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(ApplePayRecurringDetails::convert_from), + } + } + + fn convert_back(self) -> ApiFeatureMetadata { + let Self { + redirect_response, + search_tags, + apple_pay_recurring_details, + } = self; + + ApiFeatureMetadata { + redirect_response: redirect_response + .map(|redirect_response| redirect_response.convert_back()), + search_tags, + apple_pay_recurring_details: apple_pay_recurring_details + .map(|value| value.convert_back()), + } + } +} + +#[cfg(feature = "v2")] impl ApiModelToDieselModelConvertor for FeatureMetadata { fn convert_from(from: ApiFeatureMetadata) -> Self { let ApiFeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, + payment_revenue_recovery_metadata, } = from; + Self { redirect_response: redirect_response.map(RedirectResponse::convert_from), search_tags, apple_pay_recurring_details: apple_pay_recurring_details .map(ApplePayRecurringDetails::convert_from), + payment_revenue_recovery_metadata: payment_revenue_recovery_metadata + .map(PaymentRevenueRecoveryMetadata::convert_from), } } @@ -98,13 +144,17 @@ impl ApiModelToDieselModelConvertor for FeatureMetadata { redirect_response, search_tags, apple_pay_recurring_details, + payment_revenue_recovery_metadata, } = self; + ApiFeatureMetadata { redirect_response: redirect_response .map(|redirect_response| redirect_response.convert_back()), search_tags, apple_pay_recurring_details: apple_pay_recurring_details .map(|value| value.convert_back()), + payment_revenue_recovery_metadata: payment_revenue_recovery_metadata + .map(|value| value.convert_back()), } } } @@ -204,6 +254,56 @@ impl ApiModelToDieselModelConvertor for ApplePayRec } } +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor for PaymentRevenueRecoveryMetadata { + fn convert_from(from: ApiRevenueRecoveryMetadata) -> Self { + Self { + total_retry_count: from.total_retry_count, + payment_connector_transmission: from.payment_connector_transmission, + billing_connector_id: from.billing_connector_id, + active_attempt_payment_connector_id: from.active_attempt_payment_connector_id, + billing_connector_payment_details: BillingConnectorPaymentDetails::convert_from( + from.billing_connector_payment_details, + ), + payment_method_type: from.payment_method_type, + payment_method_subtype: from.payment_method_subtype, + } + } + + fn convert_back(self) -> ApiRevenueRecoveryMetadata { + ApiRevenueRecoveryMetadata { + total_retry_count: self.total_retry_count, + payment_connector_transmission: self.payment_connector_transmission, + billing_connector_id: self.billing_connector_id, + active_attempt_payment_connector_id: self.active_attempt_payment_connector_id, + billing_connector_payment_details: self + .billing_connector_payment_details + .convert_back(), + payment_method_type: self.payment_method_type, + payment_method_subtype: self.payment_method_subtype, + } + } +} + +#[cfg(feature = "v2")] +impl ApiModelToDieselModelConvertor + for BillingConnectorPaymentDetails +{ + fn convert_from(from: ApiBillingConnectorPaymentDetails) -> Self { + Self { + payment_processor_token: from.payment_processor_token, + connector_customer_id: from.connector_customer_id, + } + } + + fn convert_back(self) -> ApiBillingConnectorPaymentDetails { + ApiBillingConnectorPaymentDetails { + payment_processor_token: self.payment_processor_token, + connector_customer_id: self.connector_customer_id, + } + } +} + impl ApiModelToDieselModelConvertor for OrderDetailsWithAmount { fn convert_from(from: ApiOrderDetailsWithAmount) -> Self { let ApiOrderDetailsWithAmount { diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index cc6f1b0e411..e5c9c6fa81f 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -466,6 +466,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::BacsBankTransferInstructions, api_models::payments::RedirectResponse, api_models::payments::RequestSurchargeDetails, + api_models::payments::PaymentRevenueRecoveryMetadata, + api_models::payments::BillingConnectorPaymentDetails, + api_models::enums::PaymentConnectorTransmission, api_models::payments::PaymentAttemptResponse, api_models::payments::PaymentAttemptAmountDetails, api_models::payments::CaptureResponse, From e2043dee224bac63b4288e53475176f0941c4abb Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:10:25 +0530 Subject: [PATCH 099/133] feat(connector): [Datatrans] add mandate flow (#7245) --- .../src/connectors/datatrans.rs | 106 ++++++- .../src/connectors/datatrans/transformers.rs | 291 +++++++++++++++--- crates/hyperswitch_connectors/src/utils.rs | 21 ++ 3 files changed, 370 insertions(+), 48 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans.rs b/crates/hyperswitch_connectors/src/connectors/datatrans.rs index 6c311dd4a98..f06d7be8693 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans.rs @@ -12,6 +12,7 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, @@ -26,7 +27,7 @@ use hyperswitch_domain_models::{ router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, SetupMandateRouterData, }, }; use hyperswitch_interfaces::{ @@ -47,7 +48,10 @@ use transformers as datatrans; use crate::{ constants::headers, types::ResponseRouterData, - utils::{self, convert_amount, RefundsRequestData, RouterData as OtherRouterData}, + utils::{ + self, convert_amount, PaymentsAuthorizeRequestData, RefundsRequestData, + RouterData as OtherRouterData, + }, }; impl api::Payment for Datatrans {} @@ -191,6 +195,22 @@ impl ConnectorValidation for Datatrans { } } } + + fn validate_mandate_payment( + &self, + _pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let connector = self.id(); + match pm_data { + PaymentMethodData::Card(_) => Ok(()), + _ => Err(errors::ConnectorError::NotSupported { + message: " mandate payment".to_string(), + connector, + } + .into()), + } + } } impl ConnectorIntegration for Datatrans { @@ -202,6 +222,75 @@ impl ConnectorIntegration impl ConnectorIntegration for Datatrans { + fn get_headers( + &self, + req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + fn get_url( + &self, + _req: &SetupMandateRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}v1/transactions", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &SetupMandateRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = datatrans::DatatransPaymentsRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .set_body(types::SetupMandateType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + fn handle_response( + &self, + data: &SetupMandateRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: datatrans::DatatransResponse = res + .response + .parse_struct("Datatrans PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } } impl ConnectorIntegration for Datatrans { @@ -223,10 +312,19 @@ impl ConnectorIntegration CustomResult { let base_url = self.base_url(connectors); - if req.is_three_ds() && req.request.authentication_data.is_none() { + if req.request.payment_method_data == PaymentMethodData::MandatePayment { + // MIT + Ok(format!("{base_url}v1/transactions/authorize")) + } else if req.request.is_mandate_payment() { + // CIT Ok(format!("{base_url}v1/transactions")) } else { - Ok(format!("{base_url}v1/transactions/authorize")) + // Direct + if req.is_three_ds() && req.request.authentication_data.is_none() { + Ok(format!("{base_url}v1/transactions")) + } else { + Ok(format!("{base_url}v1/transactions/authorize")) + } } } diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index f4c3a8ae099..8a52433275e 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -1,13 +1,16 @@ use std::collections::HashMap; +use api_models::payments::{self, AdditionalPaymentData}; use common_enums::enums; use common_utils::{pii::Email, request::Method, types::MinorUnit}; use hyperswitch_domain_models::{ payment_method_data::{Card, PaymentMethodData}, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::{PaymentsAuthorizeData, ResponseId}, - router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + router_request_types::{PaymentsAuthorizeData, ResponseId, SetupMandateRequestData}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, types, }; use hyperswitch_interfaces::{ @@ -23,7 +26,7 @@ use crate::{ PaymentsSyncResponseRouterData, RefundsResponseRouterData, ResponseRouterData, }, utils::{ - get_unimplemented_payment_method_error_message, AddressDetailsData, CardData as _, + get_unimplemented_payment_method_error_message, AdditionalCardInfo, CardData as _, PaymentsAuthorizeRequestData, RouterData as _, }, }; @@ -50,13 +53,20 @@ pub struct DatatransRouterData { #[derive(Debug, Serialize, Clone)] #[serde(rename_all = "camelCase")] pub struct DatatransPaymentsRequest { - pub amount: MinorUnit, + pub amount: Option, pub currency: enums::Currency, - pub card: PlainCardDetails, + pub card: DataTransPaymentDetails, pub refno: String, pub auto_settle: bool, #[serde(skip_serializing_if = "Option::is_none")] pub redirect: Option, + pub option: Option, +} + +#[derive(Debug, Serialize, Clone)] +#[serde(rename_all = "camelCase")] +pub struct DataTransCreateAlias { + pub create_alias: bool, } #[derive(Debug, Serialize, Clone)] @@ -111,6 +121,13 @@ pub struct SyncResponse { pub res_type: TransactionType, pub status: TransactionStatus, pub detail: SyncDetails, + pub card: Option, +} + +#[derive(Serialize, Deserialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct SyncCardDetails { + pub alias: String, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -125,6 +142,14 @@ pub struct FailDetails { message: Option, } +#[derive(Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum DataTransPaymentDetails { + Cards(PlainCardDetails), + Mandate(MandateDetails), +} + #[derive(Serialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct PlainCardDetails { @@ -139,6 +164,16 @@ pub struct PlainCardDetails { pub three_ds: Option, } +#[derive(Serialize, Clone, Debug)] +#[serde(rename_all = "camelCase")] +pub struct MandateDetails { + #[serde(rename = "type")] + pub res_type: String, + pub alias: String, + pub expiry_month: Secret, + pub expiry_year: Secret, +} + #[derive(Serialize, Clone, Debug)] pub struct ThreedsInfo { cardholder: CardHolder, @@ -157,7 +192,7 @@ pub struct ThreeDSData { pub three_ds_transaction_id: Secret, pub cavv: Secret, pub eci: Option, - pub xid: Secret, + pub xid: Option>, #[serde(rename = "threeDSVersion")] pub three_ds_version: String, #[serde(rename = "authenticationResponse")] @@ -169,16 +204,6 @@ pub struct ThreeDSData { pub struct CardHolder { cardholder_name: Secret, email: Email, - #[serde(skip_serializing_if = "Option::is_none")] - bill_addr_line1: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - bill_addr_post_code: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - bill_addr_city: Option, - #[serde(skip_serializing_if = "Option::is_none")] - bill_addr_state: Option>, - #[serde(skip_serializing_if = "Option::is_none")] - bill_addr_country: Option, } #[derive(Debug, Clone, Serialize, Default, Deserialize)] @@ -246,6 +271,61 @@ impl TryFrom<(MinorUnit, T)> for DatatransRouterData { } } +impl TryFrom<&types::SetupMandateRouterData> for DatatransPaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &types::SetupMandateRouterData) -> Result { + match item.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => Ok(Self { + amount: None, + currency: item.request.currency, + card: DataTransPaymentDetails::Cards(PlainCardDetails { + res_type: "PLAIN".to_string(), + number: req_card.card_number.clone(), + expiry_month: req_card.card_exp_month.clone(), + expiry_year: req_card.get_card_expiry_year_2_digit()?, + cvv: req_card.card_cvc.clone(), + three_ds: Some(ThreeDSecureData::Cardholder(ThreedsInfo { + cardholder: CardHolder { + cardholder_name: item.get_billing_full_name()?, + email: item.get_billing_email()?, + }, + })), + }), + refno: item.connector_request_reference_id.clone(), + auto_settle: true, // zero auth doesn't support manual capture + option: Some(DataTransCreateAlias { create_alias: true }), + redirect: Some(RedirectUrls { + success_url: item.request.router_return_url.clone(), + cancel_url: item.request.router_return_url.clone(), + error_url: item.request.router_return_url.clone(), + }), + }), + PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Crypto(_) + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Datatrans"), + ))? + } + } + } +} + impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> for DatatransPaymentsRequest { @@ -254,14 +334,14 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => Ok(Self { - amount: item.amount, - currency: item.router_data.request.currency, - card: create_card_details(item, &req_card)?, - refno: item.router_data.connector_request_reference_id.clone(), - auto_settle: item.router_data.request.is_auto_capture()?, - redirect: if item.router_data.is_three_ds() - && item.router_data.request.authentication_data.is_none() + PaymentMethodData::Card(req_card) => { + let is_mandate_payment = item.router_data.request.is_mandate_payment(); + let option = + is_mandate_payment.then_some(DataTransCreateAlias { create_alias: true }); + // provides return url for only mandate payment(CIT) or 3ds through datatrans + let redirect = if is_mandate_payment + || (item.router_data.is_three_ds() + && item.router_data.request.authentication_data.is_none()) { Some(RedirectUrls { success_url: item.router_data.request.router_return_url.clone(), @@ -270,15 +350,48 @@ impl TryFrom<&DatatransRouterData<&types::PaymentsAuthorizeRouterData>> }) } else { None - }, - }), + }; + Ok(Self { + amount: Some(item.amount), + currency: item.router_data.request.currency, + card: create_card_details(item, &req_card)?, + refno: item.router_data.connector_request_reference_id.clone(), + auto_settle: item.router_data.request.is_auto_capture()?, + option, + redirect, + }) + } + PaymentMethodData::MandatePayment => { + let additional_payment_data = match item + .router_data + .request + .additional_payment_method_data + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "additional_payment_method_data", + })? { + AdditionalPaymentData::Card(card) => *card, + _ => Err(errors::ConnectorError::NotSupported { + message: "Payment Method Not Supported".to_string(), + connector: "DataTrans", + })?, + }; + Ok(Self { + amount: Some(item.amount), + currency: item.router_data.request.currency, + card: create_mandate_details(item, &additional_payment_data)?, + refno: item.router_data.connector_request_reference_id.clone(), + auto_settle: item.router_data.request.is_auto_capture()?, + option: None, + redirect: None, + }) + } PaymentMethodData::Wallet(_) | PaymentMethodData::PayLater(_) | PaymentMethodData::BankRedirect(_) | PaymentMethodData::BankDebit(_) | PaymentMethodData::BankTransfer(_) | PaymentMethodData::Crypto(_) - | PaymentMethodData::MandatePayment | PaymentMethodData::Reward | PaymentMethodData::RealTimePayment(_) | PaymentMethodData::MobilePayment(_) @@ -327,7 +440,7 @@ fn get_status(item: &DatatransResponse, is_auto_capture: bool) -> enums::Attempt fn create_card_details( item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, card: &Card, -) -> Result> { +) -> Result> { let mut details = PlainCardDetails { res_type: "PLAIN".to_string(), number: card.card_number.clone(), @@ -342,30 +455,36 @@ fn create_card_details( three_ds_transaction_id: Secret::new(auth_data.threeds_server_transaction_id.clone()), cavv: Secret::new(auth_data.cavv.clone()), eci: auth_data.eci.clone(), - xid: Secret::new( - auth_data - .ds_trans_id - .clone() - .ok_or(errors::ConnectorError::MissingRequiredField { field_name: "xid" })?, - ), + xid: auth_data.ds_trans_id.clone().map(Secret::new), three_ds_version: auth_data.message_version.to_string(), authentication_response: "Y".to_string(), })); } else if item.router_data.is_three_ds() { - let billing = item.router_data.get_billing_address()?; details.three_ds = Some(ThreeDSecureData::Cardholder(ThreedsInfo { cardholder: CardHolder { cardholder_name: item.router_data.get_billing_full_name()?, - email: item.router_data.request.get_email()?, - bill_addr_line1: billing.get_line1().ok().cloned(), - bill_addr_post_code: billing.get_zip().ok().cloned(), - bill_addr_city: billing.get_city().ok().cloned(), - bill_addr_state: billing.get_state().ok().cloned(), - bill_addr_country: billing.get_country().ok().copied(), + email: item.router_data.get_billing_email()?, }, })); } - Ok(details) + Ok(DataTransPaymentDetails::Cards(details)) +} + +fn create_mandate_details( + item: &DatatransRouterData<&types::PaymentsAuthorizeRouterData>, + additional_card_details: &payments::AdditionalCardInfo, +) -> Result> { + let alias = item.router_data.request.get_connector_mandate_id()?; + Ok(DataTransPaymentDetails::Mandate(MandateDetails { + res_type: "ALIAS".to_string(), + alias, + expiry_month: additional_card_details.card_exp_month.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "card_exp_month", + }, + )?, + expiry_year: additional_card_details.get_card_expiry_year_2_digit()?, + })) } impl From for enums::AttemptStatus { @@ -378,7 +497,15 @@ impl From for enums::AttemptStatus { TransactionStatus::Failed => Self::Failure, TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, }, - TransactionType::Credit | TransactionType::CardCheck => Self::Failure, + TransactionType::CardCheck => match item.status { + TransactionStatus::Settled + | TransactionStatus::Transmitted + | TransactionStatus::Authorized => Self::Charged, + TransactionStatus::Canceled => Self::Voided, + TransactionStatus::Failed => Self::Failure, + TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, + }, + TransactionType::Credit => Self::Failure, } } } @@ -462,6 +589,75 @@ impl } } +impl + TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + DatatransResponse, + SetupMandateRequestData, + PaymentsResponseData, + >, + ) -> Result { + // zero auth doesn't support manual capture + let status = get_status(&item.response, true); + let response = match &item.response { + DatatransResponse::ErrorResponse(error) => Err(ErrorResponse { + code: error.code.clone(), + message: error.message.clone(), + reason: Some(error.message.clone()), + attempt_status: None, + connector_transaction_id: None, + status_code: item.http_code, + }), + DatatransResponse::TransactionResponse(response) => { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.transaction_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }) + } + DatatransResponse::ThreeDSResponse(response) => { + let redirection_link = match item.data.test_mode { + Some(true) => format!("{}/v1/start", REDIRECTION_SBX_URL), + Some(false) | None => format!("{}/v1/start", REDIRECTION_PROD_URL), + }; + Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response.transaction_id.clone(), + ), + redirection_data: Box::new(Some(RedirectForm::Form { + endpoint: format!("{}/{}", redirection_link, response.transaction_id), + method: Method::Get, + form_fields: HashMap::new(), + })), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }) + } + }; + Ok(Self { + status, + response, + ..item.data + }) + } +} + impl TryFrom<&DatatransRouterData<&types::RefundsRouterData>> for DatatransRefundRequest { type Error = error_stack::Report; fn try_from( @@ -574,12 +770,19 @@ impl TryFrom> connector_transaction_id: None, }) } else { + let mandate_reference = + sync_response.card.as_ref().map(|card| MandateReference { + connector_mandate_id: Some(card.alias.clone()), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + }); Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId( sync_response.transaction_id.to_string(), ), redirection_data: Box::new(None), - mandate_reference: Box::new(None), + mandate_reference: Box::new(mandate_reference), connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index af8e89eeced..6c39667de44 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1380,6 +1380,27 @@ impl AddressDetailsData for AddressDetails { } } +pub trait AdditionalCardInfo { + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError>; +} + +impl AdditionalCardInfo for payments::AdditionalCardInfo { + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { + let binding = + self.card_exp_year + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "card_exp_year", + })?; + let year = binding.peek(); + Ok(Secret::new( + year.get(year.len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_string(), + )) + } +} + pub trait PhoneDetailsData { fn get_number(&self) -> Result, Error>; fn get_country_code(&self) -> Result; From 0b972e38abd08380b75165dfd755087769f35a62 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:10:42 +0530 Subject: [PATCH 100/133] feat(payment_methods_v2): add support for network tokenization (#7145) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 8 + crates/api_models/src/payment_methods.rs | 14 +- crates/cards/src/lib.rs | 2 +- crates/cards/src/validate.rs | 80 +++ crates/hyperswitch_connectors/Cargo.toml | 1 + .../connectors/cybersource/transformers.rs | 11 +- crates/hyperswitch_connectors/src/utils.rs | 61 +++ crates/hyperswitch_domain_models/src/lib.rs | 1 + .../src/network_tokenization.rs | 231 +++++++++ .../src/payment_method_data.rs | 120 ++++- .../src/router_data.rs | 7 +- crates/router/Cargo.toml | 4 +- .../src/connector/adyen/transformers.rs | 36 +- crates/router/src/connector/utils.rs | 79 +++ crates/router/src/core/errors.rs | 2 + crates/router/src/core/payment_methods.rs | 188 ++++++- .../payment_methods/network_tokenization.rs | 468 ++++++++++++------ .../src/core/payment_methods/transformers.rs | 1 + crates/router/src/routes/payment_methods.rs | 1 + crates/router/src/types/domain.rs | 5 + crates/router/src/types/payment_methods.rs | 4 + 21 files changed, 1147 insertions(+), 177 deletions(-) create mode 100644 crates/hyperswitch_domain_models/src/network_tokenization.rs diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 3a43b1bfca2..cedd4abcca6 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -13781,6 +13781,14 @@ } ], "nullable": true + }, + "network_tokenization": { + "allOf": [ + { + "$ref": "#/components/schemas/NetworkTokenization" + } + ], + "nullable": true } }, "additionalProperties": false diff --git a/crates/api_models/src/payment_methods.rs b/crates/api_models/src/payment_methods.rs index ba18cbba506..a228f5fbda1 100644 --- a/crates/api_models/src/payment_methods.rs +++ b/crates/api_models/src/payment_methods.rs @@ -135,6 +135,10 @@ pub struct PaymentMethodCreate { /// The billing details of the payment method #[schema(value_type = Option
)] pub billing: Option, + + /// The network tokenization configuration if applicable + #[schema(value_type = Option)] + pub network_tokenization: Option, } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -547,7 +551,15 @@ pub struct CardDetail { #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[derive( - Debug, serde::Deserialize, serde::Serialize, Clone, ToSchema, strum::EnumString, strum::Display, + Debug, + serde::Deserialize, + serde::Serialize, + Clone, + ToSchema, + strum::EnumString, + strum::Display, + Eq, + PartialEq, )] #[serde(rename_all = "snake_case")] pub enum CardType { diff --git a/crates/cards/src/lib.rs b/crates/cards/src/lib.rs index 0c0fb9d47a1..765fa7c6f8c 100644 --- a/crates/cards/src/lib.rs +++ b/crates/cards/src/lib.rs @@ -7,7 +7,7 @@ use masking::{PeekInterface, StrongSecret}; use serde::{de, Deserialize, Serialize}; use time::{util::days_in_year_month, Date, Duration, PrimitiveDateTime, Time}; -pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr}; +pub use crate::validate::{CardNumber, CardNumberStrategy, CardNumberValidationErr, NetworkToken}; #[derive(Serialize)] pub struct CardSecurityCode(StrongSecret); diff --git a/crates/cards/src/validate.rs b/crates/cards/src/validate.rs index 725d05b4807..b8b9cdbf185 100644 --- a/crates/cards/src/validate.rs +++ b/crates/cards/src/validate.rs @@ -24,6 +24,10 @@ pub struct CardNumberValidationErr(&'static str); #[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] pub struct CardNumber(StrongSecret); +//Network Token +#[derive(Clone, Debug, Default, Eq, PartialEq, Serialize)] +pub struct NetworkToken(StrongSecret); + impl CardNumber { pub fn get_card_isin(&self) -> String { self.0.peek().chars().take(6).collect::() @@ -102,6 +106,30 @@ impl CardNumber { } } +impl NetworkToken { + pub fn get_card_isin(&self) -> String { + self.0.peek().chars().take(6).collect::() + } + + pub fn get_extended_card_bin(&self) -> String { + self.0.peek().chars().take(8).collect::() + } + pub fn get_card_no(&self) -> String { + self.0.peek().chars().collect::() + } + pub fn get_last4(&self) -> String { + self.0 + .peek() + .chars() + .rev() + .take(4) + .collect::() + .chars() + .rev() + .collect::() + } +} + impl FromStr for CardNumber { type Err = CardNumberValidationErr; @@ -131,6 +159,35 @@ impl FromStr for CardNumber { } } +impl FromStr for NetworkToken { + type Err = CardNumberValidationErr; + + fn from_str(network_token: &str) -> Result { + // Valid test cards for threedsecureio + let valid_test_network_tokens = vec![ + "4000100511112003", + "6000100611111203", + "3000100811111072", + "9000100111111111", + ]; + #[cfg(not(target_arch = "wasm32"))] + let valid_test_network_tokens = match router_env_which() { + Env::Development | Env::Sandbox => valid_test_network_tokens, + Env::Production => vec![], + }; + + let network_token = network_token.split_whitespace().collect::(); + + let is_network_token_valid = sanitize_card_number(&network_token)?; + + if valid_test_network_tokens.contains(&network_token.as_str()) || is_network_token_valid { + Ok(Self(StrongSecret::new(network_token))) + } else { + Err(CardNumberValidationErr("network token invalid")) + } + } +} + pub fn sanitize_card_number(card_number: &str) -> Result { let is_card_number_valid = Ok(card_number) .and_then(validate_card_number_chars) @@ -195,6 +252,14 @@ impl TryFrom for CardNumber { } } +impl TryFrom for NetworkToken { + type Error = CardNumberValidationErr; + + fn try_from(value: String) -> Result { + Self::from_str(&value) + } +} + impl Deref for CardNumber { type Target = StrongSecret; @@ -203,6 +268,14 @@ impl Deref for CardNumber { } } +impl Deref for NetworkToken { + type Target = StrongSecret; + + fn deref(&self) -> &StrongSecret { + &self.0 + } +} + impl<'de> Deserialize<'de> for CardNumber { fn deserialize>(d: D) -> Result { let s = String::deserialize(d)?; @@ -210,6 +283,13 @@ impl<'de> Deserialize<'de> for CardNumber { } } +impl<'de> Deserialize<'de> for NetworkToken { + fn deserialize>(d: D) -> Result { + let s = String::deserialize(d)?; + Self::from_str(&s).map_err(serde::de::Error::custom) + } +} + pub enum CardNumberStrategy {} impl Strategy for CardNumberStrategy diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 6b76e6ac195..13d5266dabd 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -9,6 +9,7 @@ license.workspace = true frm = ["hyperswitch_domain_models/frm", "hyperswitch_interfaces/frm"] payouts = ["hyperswitch_domain_models/payouts", "api_models/payouts", "hyperswitch_interfaces/payouts"] v1 = ["api_models/v1", "hyperswitch_domain_models/v1", "common_utils/v1"] +v2 = ["api_models/v2", "hyperswitch_domain_models/v2", "common_utils/v2"] [dependencies] actix-http = "3.6.0" diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index b42c02606c1..b953fa73754 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -18,6 +18,7 @@ use hyperswitch_domain_models::{ types::PayoutsRouterData, }; use hyperswitch_domain_models::{ + network_tokenization::NetworkTokenNumber, payment_method_data::{ ApplePayWalletData, GooglePayWalletData, NetworkTokenData, PaymentMethodData, SamsungPayWalletData, WalletData, @@ -430,7 +431,7 @@ pub struct CaptureOptions { #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct NetworkTokenizedCard { - number: cards::CardNumber, + number: NetworkTokenNumber, expiration_month: Secret, expiration_year: Secret, cryptogram: Option>, @@ -1400,10 +1401,10 @@ impl let payment_information = PaymentInformation::NetworkToken(Box::new(NetworkTokenPaymentInformation { tokenized_card: NetworkTokenizedCard { - number: token_data.token_number, - expiration_month: token_data.token_exp_month, - expiration_year: token_data.token_exp_year, - cryptogram: token_data.token_cryptogram.clone(), + number: token_data.get_network_token(), + expiration_month: token_data.get_network_token_expiry_month(), + expiration_year: token_data.get_network_token_expiry_year(), + cryptogram: token_data.get_cryptogram().clone(), transaction_type: "1".to_string(), }, })); diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 6c39667de44..b14abec9d3a 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -19,6 +19,7 @@ use common_utils::{ use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ address::{Address, AddressDetails, PhoneDetails}, + network_tokenization::NetworkTokenNumber, payment_method_data::{self, Card, CardDetailsForNetworkTransactionId, PaymentMethodData}, router_data::{ ApplePayPredecryptData, ErrorResponse, PaymentMethodToken, RecurringMandatePaymentData, @@ -2957,13 +2958,24 @@ impl CardData for api_models::payouts::CardPayout { pub trait NetworkTokenData { fn get_card_issuer(&self) -> Result; fn get_expiry_year_4_digit(&self) -> Secret; + fn get_network_token(&self) -> NetworkTokenNumber; + fn get_network_token_expiry_month(&self) -> Secret; + fn get_network_token_expiry_year(&self) -> Secret; + fn get_cryptogram(&self) -> Option>; } impl NetworkTokenData for payment_method_data::NetworkTokenData { + #[cfg(feature = "v1")] fn get_card_issuer(&self) -> Result { get_card_issuer(self.token_number.peek()) } + #[cfg(feature = "v2")] + fn get_card_issuer(&self) -> Result { + get_card_issuer(self.network_token.peek()) + } + + #[cfg(feature = "v1")] fn get_expiry_year_4_digit(&self) -> Secret { let mut year = self.token_exp_year.peek().clone(); if year.len() == 2 { @@ -2971,4 +2983,53 @@ impl NetworkTokenData for payment_method_data::NetworkTokenData { } Secret::new(year) } + + #[cfg(feature = "v2")] + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.network_token_exp_year.peek().clone(); + if year.len() == 2 { + year = format!("20{}", year); + } + Secret::new(year) + } + + #[cfg(feature = "v1")] + fn get_network_token(&self) -> NetworkTokenNumber { + self.token_number.clone() + } + + #[cfg(feature = "v2")] + fn get_network_token(&self) -> NetworkTokenNumber { + self.network_token.clone() + } + + #[cfg(feature = "v1")] + fn get_network_token_expiry_month(&self) -> Secret { + self.token_exp_month.clone() + } + + #[cfg(feature = "v2")] + fn get_network_token_expiry_month(&self) -> Secret { + self.network_token_exp_month.clone() + } + + #[cfg(feature = "v1")] + fn get_network_token_expiry_year(&self) -> Secret { + self.token_exp_year.clone() + } + + #[cfg(feature = "v2")] + fn get_network_token_expiry_year(&self) -> Secret { + self.network_token_exp_year.clone() + } + + #[cfg(feature = "v1")] + fn get_cryptogram(&self) -> Option> { + self.token_cryptogram.clone() + } + + #[cfg(feature = "v2")] + fn get_cryptogram(&self) -> Option> { + self.cryptogram.clone() + } } diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index b2546e3a049..6c8f499388a 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -11,6 +11,7 @@ pub mod mandates; pub mod merchant_account; pub mod merchant_connector_account; pub mod merchant_key_store; +pub mod network_tokenization; pub mod payment_address; pub mod payment_method_data; pub mod payment_methods; diff --git a/crates/hyperswitch_domain_models/src/network_tokenization.rs b/crates/hyperswitch_domain_models/src/network_tokenization.rs new file mode 100644 index 00000000000..bf87c750509 --- /dev/null +++ b/crates/hyperswitch_domain_models/src/network_tokenization.rs @@ -0,0 +1,231 @@ +use std::fmt::Debug; + +use api_models::enums as api_enums; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +use cards::CardNumber; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use cards::{CardNumber, NetworkToken}; +use common_utils::id_type; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardData { + pub card_number: CardNumber, + pub exp_month: Secret, + pub exp_year: Secret, + pub card_security_code: Secret, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardData { + pub card_number: CardNumber, + pub exp_month: Secret, + pub exp_year: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + pub card_security_code: Option>, +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderData { + pub consent_id: String, + pub customer_id: id_type::CustomerId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct OrderData { + pub consent_id: String, + pub customer_id: id_type::GlobalCustomerId, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApiPayload { + pub service: String, + pub card_data: Secret, //encrypted card data + pub order_data: OrderData, + pub key_id: String, + pub should_send_token: bool, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct CardNetworkTokenResponse { + pub payload: Secret, //encrypted payload +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CardNetworkTokenResponsePayload { + pub card_brand: api_enums::CardNetwork, + pub card_fingerprint: Option>, + pub card_reference: String, + pub correlation_id: String, + pub customer_id: String, + pub par: String, + pub token: CardNumber, + pub token_expiry_month: Secret, + pub token_expiry_year: Secret, + pub token_isin: String, + pub token_last_four: String, + pub token_status: String, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct GenerateNetworkTokenResponsePayload { + pub card_brand: api_enums::CardNetwork, + pub card_fingerprint: Option>, + pub card_reference: String, + pub correlation_id: String, + pub customer_id: String, + pub par: String, + pub token: NetworkToken, + pub token_expiry_month: Secret, + pub token_expiry_year: Secret, + pub token_isin: String, + pub token_last_four: String, + pub token_status: String, +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Serialize)] +pub struct GetCardToken { + pub card_reference: String, + pub customer_id: id_type::CustomerId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Serialize)] +pub struct GetCardToken { + pub card_reference: String, + pub customer_id: id_type::GlobalCustomerId, +} +#[derive(Debug, Deserialize)] +pub struct AuthenticationDetails { + pub cryptogram: Secret, + pub token: CardNumber, //network token +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct TokenDetails { + pub exp_month: Secret, + pub exp_year: Secret, +} + +#[derive(Debug, Deserialize)] +pub struct TokenResponse { + pub authentication_details: AuthenticationDetails, + pub network: api_enums::CardNetwork, + pub token_details: TokenDetails, +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Serialize, Deserialize)] +pub struct DeleteCardToken { + pub card_reference: String, //network token requestor ref id + pub customer_id: id_type::CustomerId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Serialize, Deserialize)] +pub struct DeleteCardToken { + pub card_reference: String, //network token requestor ref id + pub customer_id: id_type::GlobalCustomerId, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum DeleteNetworkTokenStatus { + Success, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct NetworkTokenErrorInfo { + pub code: String, + pub developer_message: String, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct NetworkTokenErrorResponse { + pub error_message: String, + pub error_info: NetworkTokenErrorInfo, +} + +#[derive(Debug, Deserialize, Eq, PartialEq)] +pub struct DeleteNetworkTokenResponse { + pub status: DeleteNetworkTokenStatus, +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +#[derive(Debug, Serialize, Deserialize)] +pub struct CheckTokenStatus { + pub card_reference: String, + pub customer_id: id_type::CustomerId, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Debug, Serialize, Deserialize)] +pub struct CheckTokenStatus { + pub card_reference: String, + pub customer_id: id_type::GlobalCustomerId, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "UPPERCASE")] +pub enum TokenStatus { + Active, + Inactive, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct CheckTokenStatusResponsePayload { + pub token_expiry_month: Secret, + pub token_expiry_year: Secret, + pub token_status: TokenStatus, +} + +#[derive(Debug, Deserialize)] +pub struct CheckTokenStatusResponse { + pub payload: CheckTokenStatusResponsePayload, +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +pub type NetworkTokenNumber = CardNumber; + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub type NetworkTokenNumber = NetworkToken; diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index e96368080d9..2f8375b47c0 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -1,5 +1,6 @@ use api_models::{ - mandates, payment_methods, + mandates, + payment_methods::{self}, payments::{ additional_info as payment_additional_types, AmazonPayRedirectData, ExtendedCardInfo, }, @@ -587,6 +588,10 @@ pub struct SepaAndBacsBillingDetails { pub name: Secret, } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] pub struct NetworkTokenData { pub token_number: cards::CardNumber, @@ -602,6 +607,37 @@ pub struct NetworkTokenData { pub eci: Option, } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] +pub struct NetworkTokenData { + pub network_token: cards::NetworkToken, + pub network_token_exp_month: Secret, + pub network_token_exp_year: Secret, + pub cryptogram: Option>, + pub card_issuer: Option, //since network token is tied to card, so its issuer will be same as card issuer + pub card_network: Option, + pub card_type: Option, + pub card_issuing_country: Option, + pub bank_code: Option, + pub card_holder_name: Option>, + pub nick_name: Option>, + pub eci: Option, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize, Default)] +pub struct NetworkTokenDetails { + pub network_token: cards::NetworkToken, + pub network_token_exp_month: Secret, + pub network_token_exp_year: Secret, + pub card_issuer: Option, //since network token is tied to card, so its issuer will be same as card issuer + pub card_network: Option, + pub card_type: Option, + pub card_issuing_country: Option, + pub card_holder_name: Option>, + pub nick_name: Option>, +} + #[derive(Eq, PartialEq, Clone, Debug, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum MobilePaymentData { @@ -1726,3 +1762,85 @@ impl From for payment_methods::PaymentMethodDataWalletInfo } } } + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub enum PaymentMethodsData { + Card(CardDetailsPaymentMethod), + BankDetails(payment_methods::PaymentMethodDataBankCreds), //PaymentMethodDataBankCreds and its transformations should be moved to the domain models + WalletDetails(payment_methods::PaymentMethodDataWalletInfo), //PaymentMethodDataWalletInfo and its transformations should be moved to the domain models + NetworkToken(NetworkTokenDetailsPaymentMethod), +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct NetworkTokenDetailsPaymentMethod { + pub last4_digits: Option, + pub issuer_country: Option, + pub network_token_expiry_month: Option>, + pub network_token_expiry_year: Option>, + pub nick_name: Option>, + pub card_holder_name: Option>, + pub card_isin: Option, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + #[serde(default = "saved_in_locker_default")] + pub saved_to_locker: bool, +} + +fn saved_in_locker_default() -> bool { + true +} + +#[derive(Clone, Debug, Eq, PartialEq, serde::Deserialize, serde::Serialize)] +pub struct CardDetailsPaymentMethod { + pub last4_digits: Option, + pub issuer_country: Option, + pub expiry_month: Option>, + pub expiry_year: Option>, + pub nick_name: Option>, + pub card_holder_name: Option>, + pub card_isin: Option, + pub card_issuer: Option, + pub card_network: Option, + pub card_type: Option, + #[serde(default = "saved_in_locker_default")] + pub saved_to_locker: bool, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for CardDetailsPaymentMethod { + fn from(item: payment_methods::CardDetail) -> Self { + Self { + issuer_country: item.card_issuing_country.map(|c| c.to_string()), + last4_digits: Some(item.card_number.get_last4()), + expiry_month: Some(item.card_exp_month), + expiry_year: Some(item.card_exp_year), + card_holder_name: item.card_holder_name, + nick_name: item.nick_name, + card_isin: None, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type.map(|card| card.to_string()), + saved_to_locker: true, + } + } +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +impl From for NetworkTokenDetailsPaymentMethod { + fn from(item: NetworkTokenDetails) -> Self { + Self { + issuer_country: item.card_issuing_country.map(|c| c.to_string()), + last4_digits: Some(item.network_token.get_last4()), + network_token_expiry_month: Some(item.network_token_exp_month), + network_token_expiry_year: Some(item.network_token_exp_year), + card_holder_name: item.card_holder_name, + nick_name: item.nick_name, + card_isin: None, + card_issuer: item.card_issuer, + card_network: item.card_network, + card_type: item.card_type.map(|card| card.to_string()), + saved_to_locker: true, + } + } +} diff --git a/crates/hyperswitch_domain_models/src/router_data.rs b/crates/hyperswitch_domain_models/src/router_data.rs index 0c90b0d0244..e3a8264217c 100644 --- a/crates/hyperswitch_domain_models/src/router_data.rs +++ b/crates/hyperswitch_domain_models/src/router_data.rs @@ -9,7 +9,10 @@ use common_utils::{ use error_stack::ResultExt; use masking::{ExposeInterface, Secret}; -use crate::{payment_address::PaymentAddress, payment_method_data, payments}; +use crate::{ + network_tokenization::NetworkTokenNumber, payment_address::PaymentAddress, payment_method_data, + payments, +}; #[cfg(feature = "v2")] use crate::{ payments::{ @@ -294,7 +297,7 @@ pub struct PazeDecryptedData { #[derive(Debug, Clone, serde::Deserialize)] #[serde(rename_all = "camelCase")] pub struct PazeToken { - pub payment_token: cards::CardNumber, + pub payment_token: NetworkTokenNumber, pub token_expiration_month: Secret, pub token_expiration_year: Secret, pub payment_account_reference: Secret, diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index 6257d3e0832..d590797d642 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -33,8 +33,8 @@ payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_connectors payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] -v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2"] -v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1"] +v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2"] +v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1", "hyperswitch_connectors/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 465730cca64..c9bfdbf3d4a 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -4,7 +4,9 @@ use api_models::{enums, payments, webhooks}; use cards::CardNumber; use common_utils::{errors::ParsingError, ext_traits::Encode, id_type, pii, types::MinorUnit}; use error_stack::{report, ResultExt}; -use hyperswitch_domain_models::router_request_types::SubmitEvidenceRequestData; +use hyperswitch_domain_models::{ + network_tokenization::NetworkTokenNumber, router_request_types::SubmitEvidenceRequestData, +}; use masking::{ExposeInterface, PeekInterface}; use reqwest::Url; use serde::{Deserialize, Serialize}; @@ -637,6 +639,7 @@ pub enum AdyenPaymentMethod<'a> { PayEasy(Box), Pix(Box), NetworkToken(Box), + AdyenPaze(Box), } #[derive(Debug, Clone, Serialize)] @@ -1142,6 +1145,21 @@ pub struct AdyenCard { network_payment_reference: Option>, } +#[serde_with::skip_serializing_none] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct AdyenPazeData { + #[serde(rename = "type")] + payment_type: PaymentType, + number: NetworkTokenNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Option>, + holder_name: Option>, + brand: Option, //Mandatory for mandate using network_txns_id + network_payment_reference: Option>, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum CardBrand { @@ -1241,7 +1259,7 @@ pub struct AdyenApplePay { pub struct AdyenNetworkTokenData { #[serde(rename = "type")] payment_type: PaymentType, - number: CardNumber, + number: NetworkTokenNumber, expiry_month: Secret, expiry_year: Secret, holder_name: Option>, @@ -2200,7 +2218,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> } domain::WalletData::Paze(_) => match item.payment_method_token.clone() { Some(types::PaymentMethodToken::PazeDecrypt(paze_decrypted_data)) => { - let data = AdyenCard { + let data = AdyenPazeData { payment_type: PaymentType::NetworkToken, number: paze_decrypted_data.token.payment_token, expiry_month: paze_decrypted_data.token.token_expiration_month, @@ -2214,7 +2232,7 @@ impl TryFrom<(&domain::WalletData, &types::PaymentsAuthorizeRouterData)> .and_then(get_adyen_card_network), network_payment_reference: None, }; - Ok(AdyenPaymentMethod::AdyenCard(Box::new(data))) + Ok(AdyenPaymentMethod::AdyenPaze(Box::new(data))) } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Cybersource"), @@ -2725,8 +2743,8 @@ impl let card_holder_name = item.router_data.get_optional_billing_full_name(); let adyen_network_token = AdyenNetworkTokenData { payment_type: PaymentType::NetworkToken, - number: token_data.token_number.clone(), - expiry_month: token_data.token_exp_month.clone(), + number: token_data.get_network_token(), + expiry_month: token_data.get_network_token_expiry_month(), expiry_year: token_data.get_expiry_year_4_digit(), holder_name: card_holder_name, brand: Some(brand), // FIXME: Remove hardcoding @@ -5577,8 +5595,8 @@ impl TryFrom<(&domain::NetworkTokenData, Option>)> for AdyenPayme ) -> Result { let adyen_network_token = AdyenNetworkTokenData { payment_type: PaymentType::NetworkToken, - number: token_data.token_number.clone(), - expiry_month: token_data.token_exp_month.clone(), + number: token_data.get_network_token(), + expiry_month: token_data.get_network_token_expiry_month(), expiry_year: token_data.get_expiry_year_4_digit(), holder_name: card_holder_name, brand: None, // FIXME: Remove hardcoding @@ -5627,7 +5645,7 @@ impl directory_response: "Y".to_string(), authentication_response: "Y".to_string(), token_authentication_verification_value: token_data - .token_cryptogram + .get_cryptogram() .clone() .unwrap_or_default(), eci: Some("02".to_string()), diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 28f5d016e5a..0aca263b5c7 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -22,6 +22,7 @@ use diesel_models::{enums, types::OrderDetailsWithAmount}; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ mandates, + network_tokenization::NetworkTokenNumber, payments::payment_attempt::PaymentAttempt, router_request_types::{ AuthoriseIntegrityObject, CaptureIntegrityObject, RefundIntegrityObject, @@ -3181,13 +3182,30 @@ pub fn get_refund_integrity_object( pub trait NetworkTokenData { fn get_card_issuer(&self) -> Result; fn get_expiry_year_4_digit(&self) -> Secret; + fn get_network_token(&self) -> NetworkTokenNumber; + fn get_network_token_expiry_month(&self) -> Secret; + fn get_network_token_expiry_year(&self) -> Secret; + fn get_cryptogram(&self) -> Option>; } impl NetworkTokenData for domain::NetworkTokenData { + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] fn get_card_issuer(&self) -> Result { get_card_issuer(self.token_number.peek()) } + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_card_issuer(&self) -> Result { + get_card_issuer(self.network_token.peek()) + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] fn get_expiry_year_4_digit(&self) -> Secret { let mut year = self.token_exp_year.peek().clone(); if year.len() == 2 { @@ -3195,6 +3213,67 @@ impl NetworkTokenData for domain::NetworkTokenData { } Secret::new(year) } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.network_token_exp_year.peek().clone(); + if year.len() == 2 { + year = format!("20{}", year); + } + Secret::new(year) + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + fn get_network_token(&self) -> NetworkTokenNumber { + self.token_number.clone() + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_network_token(&self) -> NetworkTokenNumber { + self.network_token.clone() + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + fn get_network_token_expiry_month(&self) -> Secret { + self.token_exp_month.clone() + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_network_token_expiry_month(&self) -> Secret { + self.network_token_exp_month.clone() + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + fn get_network_token_expiry_year(&self) -> Secret { + self.token_exp_year.clone() + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_network_token_expiry_year(&self) -> Secret { + self.network_token_exp_year.clone() + } + + #[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") + ))] + fn get_cryptogram(&self) -> Option> { + self.token_cryptogram.clone() + } + + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] + fn get_cryptogram(&self) -> Option> { + self.cryptogram.clone() + } } pub fn convert_uppercase<'de, D, T>(v: D) -> Result diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 506ce557192..3dbaa999219 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -442,4 +442,6 @@ pub enum NetworkTokenizationError { DeleteNetworkTokenFailed, #[error("Network token service not configured")] NetworkTokenizationServiceNotConfigured, + #[error("Failed while calling Network Token Service API")] + ApiError, } diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 5bdb0f0819e..6ebc7bde5e4 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -47,9 +47,9 @@ use hyperswitch_domain_models::api::{GenericLinks, GenericLinksData}; feature = "customer_v2" ))] use hyperswitch_domain_models::mandates::CommonMandateReference; -use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] -use masking::ExposeInterface; +use hyperswitch_domain_models::payment_method_data; +use hyperswitch_domain_models::payments::{payment_attempt::PaymentAttempt, PaymentIntent}; use masking::{PeekInterface, Secret}; use router_env::{instrument, tracing}; use time::Duration; @@ -737,6 +737,7 @@ pub(crate) async fn get_payment_method_create_request( card_detail, ), billing: None, + network_tokenization: None, }; Ok(payment_method_request) } @@ -853,6 +854,7 @@ pub async fn create_payment_method( req: api::PaymentMethodCreate, merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, + profile: &domain::Profile, ) -> RouterResponse { use common_utils::ext_traits::ValueExt; @@ -910,6 +912,9 @@ pub async fn create_payment_method( let payment_method_data = pm_types::PaymentMethodVaultingData::from(req.payment_method_data); + let payment_method_data = + populate_bin_details_for_payment_method(state, &payment_method_data).await; + let vaulting_result = vault_payment_method( state, &payment_method_data, @@ -919,6 +924,25 @@ pub async fn create_payment_method( ) .await; + let network_tokenization_resp = network_tokenize_and_vault_the_pmd( + state, + &payment_method_data, + merchant_account, + key_store, + req.network_tokenization, + profile.is_network_tokenization_enabled, + &customer_id, + ) + .await + .map_err(|e| { + services::logger::error!( + "Failed to network tokenize the payment method for customer: {}. Error: {} ", + customer_id.get_string_repr(), + e + ); + }) + .ok(); + let response = match vaulting_result { Ok((vaulting_resp, fingerprint_id)) => { let pm_update = create_pm_additional_data_update( @@ -929,6 +953,7 @@ pub async fn create_payment_method( Some(req.payment_method_type), Some(req.payment_method_subtype), Some(fingerprint_id), + network_tokenization_resp, ) .await .attach_printable("Unable to create Payment method data")?; @@ -972,6 +997,145 @@ pub async fn create_payment_method( Ok(services::ApplicationResponse::Json(response)) } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +#[derive(Clone, Debug)] +pub struct NetworkTokenPaymentMethodDetails { + network_token_requestor_reference_id: String, + network_token_locker_id: String, + network_token_pmd: Encryptable>, +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn network_tokenize_and_vault_the_pmd( + state: &SessionState, + payment_method_data: &pm_types::PaymentMethodVaultingData, + merchant_account: &domain::MerchantAccount, + key_store: &domain::MerchantKeyStore, + network_tokenization: Option, + network_tokenization_enabled_for_profile: bool, + customer_id: &id_type::GlobalCustomerId, +) -> RouterResult { + when(!network_tokenization_enabled_for_profile, || { + Err(report!(errors::ApiErrorResponse::NotSupported { + message: "Network Tokenization is not enabled for this payment method".to_string() + })) + })?; + + let is_network_tokenization_enabled_for_pm = network_tokenization + .as_ref() + .map(|nt| matches!(nt.enable, common_enums::NetworkTokenizationToggle::Enable)) + .unwrap_or(false); + + let card_data = match payment_method_data { + pm_types::PaymentMethodVaultingData::Card(data) + if is_network_tokenization_enabled_for_pm => + { + Ok(data) + } + _ => Err(report!(errors::ApiErrorResponse::NotSupported { + message: "Network Tokenization is not supported for this payment method".to_string() + })), + }?; + + let (resp, network_token_req_ref_id) = + network_tokenization::make_card_network_tokenization_request(state, card_data, customer_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to generate network token")?; + + let network_token_vaulting_data = pm_types::PaymentMethodVaultingData::NetworkToken(resp); + let vaulting_resp = vault::add_payment_method_to_vault( + state, + merchant_account, + &network_token_vaulting_data, + None, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to vault the network token data")?; + + let key_manager_state = &(state).into(); + let network_token = match network_token_vaulting_data { + pm_types::PaymentMethodVaultingData::Card(card) => { + payment_method_data::PaymentMethodsData::Card( + payment_method_data::CardDetailsPaymentMethod::from(card.clone()), + ) + } + pm_types::PaymentMethodVaultingData::NetworkToken(network_token) => { + payment_method_data::PaymentMethodsData::NetworkToken( + payment_method_data::NetworkTokenDetailsPaymentMethod::from(network_token.clone()), + ) + } + }; + + let network_token_pmd = + cards::create_encrypted_data(key_manager_state, key_store, network_token) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to encrypt Payment method data")?; + + Ok(NetworkTokenPaymentMethodDetails { + network_token_requestor_reference_id: network_token_req_ref_id, + network_token_locker_id: vaulting_resp.vault_id.get_string_repr().clone(), + network_token_pmd, + }) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn populate_bin_details_for_payment_method( + state: &SessionState, + payment_method_data: &pm_types::PaymentMethodVaultingData, +) -> pm_types::PaymentMethodVaultingData { + match payment_method_data { + pm_types::PaymentMethodVaultingData::Card(card) => { + let card_isin = card.card_number.get_card_isin(); + + if card.card_issuer.is_some() + && card.card_network.is_some() + && card.card_type.is_some() + && card.card_issuing_country.is_some() + { + pm_types::PaymentMethodVaultingData::Card(card.clone()) + } else { + let card_info = state + .store + .get_card_info(&card_isin) + .await + .map_err(|error| services::logger::error!(card_info_error=?error)) + .ok() + .flatten(); + + pm_types::PaymentMethodVaultingData::Card(payment_methods::CardDetail { + card_number: card.card_number.clone(), + card_exp_month: card.card_exp_month.clone(), + card_exp_year: card.card_exp_year.clone(), + card_holder_name: card.card_holder_name.clone(), + nick_name: card.nick_name.clone(), + card_issuing_country: card_info.as_ref().and_then(|val| { + val.card_issuing_country + .as_ref() + .map(|c| api_enums::CountryAlpha2::from_str(c)) + .transpose() + .ok() + .flatten() + }), + card_network: card_info.as_ref().and_then(|val| val.card_network.clone()), + card_issuer: card_info.as_ref().and_then(|val| val.card_issuer.clone()), + card_type: card_info.as_ref().and_then(|val| { + val.card_type + .as_ref() + .map(|c| payment_methods::CardType::from_str(c)) + .transpose() + .ok() + .flatten() + }), + }) + } + } + _ => payment_method_data.clone(), + } +} + #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] #[instrument(skip_all)] pub async fn payment_method_intent_create( @@ -1293,6 +1457,7 @@ pub async fn create_payment_method_for_intent( Ok(response) } +#[allow(clippy::too_many_arguments)] #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] pub async fn create_pm_additional_data_update( pmd: &pm_types::PaymentMethodVaultingData, @@ -1302,10 +1467,18 @@ pub async fn create_pm_additional_data_update( payment_method_type: Option, payment_method_subtype: Option, vault_fingerprint_id: Option, + nt_data: Option, ) -> RouterResult { let card = match pmd { pm_types::PaymentMethodVaultingData::Card(card) => { - api::PaymentMethodsData::Card(card.clone().into()) + payment_method_data::PaymentMethodsData::Card( + payment_method_data::CardDetailsPaymentMethod::from(card.clone()), + ) + } + pm_types::PaymentMethodVaultingData::NetworkToken(network_token) => { + payment_method_data::PaymentMethodsData::NetworkToken( + payment_method_data::NetworkTokenDetailsPaymentMethod::from(network_token.clone()), + ) } }; let key_manager_state = &(state).into(); @@ -1321,9 +1494,11 @@ pub async fn create_pm_additional_data_update( payment_method_type_v2: payment_method_type, payment_method_subtype, payment_method_data: Some(pmd.into()), - network_token_requestor_reference_id: None, - network_token_locker_id: None, - network_token_payment_method_data: None, + network_token_requestor_reference_id: nt_data + .clone() + .map(|data| data.network_token_requestor_reference_id), + network_token_locker_id: nt_data.clone().map(|data| data.network_token_locker_id), + network_token_payment_method_data: nt_data.map(|data| data.network_token_pmd.into()), locker_fingerprint_id: vault_fingerprint_id, }; @@ -1633,6 +1808,7 @@ pub async fn update_payment_method_core( payment_method.get_payment_method_type(), payment_method.get_payment_method_subtype(), Some(fingerprint_id), + None, ) .await .attach_printable("Unable to create Payment method data")?; diff --git a/crates/router/src/core/payment_methods/network_tokenization.rs b/crates/router/src/core/payment_methods/network_tokenization.rs index bb730caad2b..6799a1cf5ba 100644 --- a/crates/router/src/core/payment_methods/network_tokenization.rs +++ b/crates/router/src/core/payment_methods/network_tokenization.rs @@ -1,7 +1,9 @@ +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use std::fmt::Debug; -use api_models::{enums as api_enums, payment_methods::PaymentMethodsData}; -use cards::CardNumber; +use api_models::payment_methods as api_payment_methods; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use cards::{CardNumber, NetworkToken}; use common_utils::{ errors::CustomResult, ext_traits::{BytesExt, Encode}, @@ -9,10 +11,17 @@ use common_utils::{ metrics::utils::record_operation_time, request::RequestContent, }; +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] use error_stack::ResultExt; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use error_stack::{report, ResultExt}; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use hyperswitch_domain_models::payment_method_data::NetworkTokenDetails; use josekit::jwe; use masking::{ExposeInterface, Mask, PeekInterface, Secret}; -use serde::{Deserialize, Serialize}; use super::transformers::DeleteCardResp; use crate::{ @@ -24,142 +33,132 @@ use crate::{ types::{api, domain}, }; -#[derive(Debug, Serialize, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CardData { - card_number: CardNumber, - exp_month: Secret, - exp_year: Secret, - card_security_code: Secret, -} +pub const NETWORK_TOKEN_SERVICE: &str = "NETWORK_TOKEN"; -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct OrderData { - consent_id: String, +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] +pub async fn mk_tokenization_req( + state: &routes::SessionState, + payload_bytes: &[u8], customer_id: id_type::CustomerId, -} - -#[derive(Debug, Serialize)] -#[serde(rename_all = "camelCase")] -pub struct ApiPayload { - service: String, - card_data: Secret, //encrypted card data - order_data: OrderData, - key_id: String, - should_send_token: bool, -} - -#[derive(Debug, Deserialize, Eq, PartialEq)] -pub struct CardNetworkTokenResponse { - payload: Secret, //encrypted payload -} + tokenization_service: &settings::NetworkTokenizationService, +) -> CustomResult< + (domain::CardNetworkTokenResponsePayload, Option), + errors::NetworkTokenizationError, +> { + let enc_key = tokenization_service.public_key.peek().clone(); -#[derive(Debug, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CardNetworkTokenResponsePayload { - pub card_brand: api_enums::CardNetwork, - pub card_fingerprint: Option>, - pub card_reference: String, - pub correlation_id: String, - pub customer_id: String, - pub par: String, - pub token: CardNumber, - pub token_expiry_month: Secret, - pub token_expiry_year: Secret, - pub token_isin: String, - pub token_last_four: String, - pub token_status: String, -} + let key_id = tokenization_service.key_id.clone(); -#[derive(Debug, Serialize)] -pub struct GetCardToken { - card_reference: String, - customer_id: id_type::CustomerId, -} -#[derive(Debug, Deserialize)] -pub struct AuthenticationDetails { - cryptogram: Secret, - token: CardNumber, //network token -} + let jwt = encryption::encrypt_jwe( + payload_bytes, + enc_key, + services::EncryptionAlgorithm::A128GCM, + Some(key_id.as_str()), + ) + .await + .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed) + .attach_printable("Error on jwe encrypt")?; -#[derive(Debug, Serialize, Deserialize)] -pub struct TokenDetails { - exp_month: Secret, - exp_year: Secret, -} + let order_data = domain::OrderData { + consent_id: uuid::Uuid::new_v4().to_string(), + customer_id, + }; -#[derive(Debug, Deserialize)] -pub struct TokenResponse { - authentication_details: AuthenticationDetails, - network: api_enums::CardNetwork, - token_details: TokenDetails, -} + let api_payload = domain::ApiPayload { + service: NETWORK_TOKEN_SERVICE.to_string(), + card_data: Secret::new(jwt), + order_data, + key_id, + should_send_token: true, + }; -#[derive(Debug, Serialize, Deserialize)] -pub struct DeleteCardToken { - card_reference: String, //network token requestor ref id - customer_id: id_type::CustomerId, -} + let mut request = services::Request::new( + services::Method::Post, + tokenization_service.generate_token_url.as_str(), + ); + request.add_header(headers::CONTENT_TYPE, "application/json".into()); + request.add_header( + headers::AUTHORIZATION, + tokenization_service + .token_service_api_key + .peek() + .clone() + .into_masked(), + ); + request.add_default_headers(); -#[derive(Debug, Deserialize, Eq, PartialEq)] -#[serde(rename_all = "UPPERCASE")] -pub enum DeleteNetworkTokenStatus { - Success, -} + request.set_body(RequestContent::Json(Box::new(api_payload))); -#[derive(Debug, Deserialize, Eq, PartialEq)] -pub struct NetworkTokenErrorInfo { - code: String, - developer_message: String, -} + logger::info!("Request to generate token: {:?}", request); -#[derive(Debug, Deserialize, Eq, PartialEq)] -pub struct NetworkTokenErrorResponse { - error_message: String, - error_info: NetworkTokenErrorInfo, -} + let response = services::call_connector_api(state, request, "generate_token") + .await + .change_context(errors::NetworkTokenizationError::ApiError); -#[derive(Debug, Deserialize, Eq, PartialEq)] -pub struct DeleteNetworkTokenResponse { - status: DeleteNetworkTokenStatus, -} + let res = response + .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) + .attach_printable("Error while receiving response") + .and_then(|inner| match inner { + Err(err_res) => { + let parsed_error: domain::NetworkTokenErrorResponse = err_res + .response + .parse_struct("Card Network Tokenization Response") + .change_context( + errors::NetworkTokenizationError::ResponseDeserializationFailed, + )?; + logger::error!( + error_code = %parsed_error.error_info.code, + developer_message = %parsed_error.error_info.developer_message, + "Network tokenization error: {}", + parsed_error.error_message + ); + Err(errors::NetworkTokenizationError::ResponseDeserializationFailed) + .attach_printable(format!("Response Deserialization Failed: {err_res:?}")) + } + Ok(res) => Ok(res), + }) + .inspect_err(|err| { + logger::error!("Error while deserializing response: {:?}", err); + })?; -#[derive(Debug, Serialize, Deserialize)] -pub struct CheckTokenStatus { - card_reference: String, - customer_id: id_type::CustomerId, -} + let network_response: domain::CardNetworkTokenResponse = res + .response + .parse_struct("Card Network Tokenization Response") + .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; -#[derive(Debug, Deserialize)] -#[serde(rename_all = "UPPERCASE")] -pub enum TokenStatus { - Active, - Inactive, -} + let dec_key = tokenization_service.private_key.peek().clone(); -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct CheckTokenStatusResponsePayload { - token_expiry_month: Secret, - token_expiry_year: Secret, - token_status: TokenStatus, -} + let card_network_token_response = services::decrypt_jwe( + network_response.payload.peek(), + services::KeyIdCheck::SkipKeyIdCheck, + dec_key, + jwe::RSA_OAEP_256, + ) + .await + .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed) + .attach_printable( + "Failed to decrypt the tokenization response from the tokenization service", + )?; -#[derive(Debug, Deserialize)] -pub struct CheckTokenStatusResponse { - payload: CheckTokenStatusResponsePayload, + let cn_response: domain::CardNetworkTokenResponsePayload = + serde_json::from_str(&card_network_token_response) + .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; + Ok((cn_response.clone(), Some(cn_response.card_reference))) } -pub const NETWORK_TOKEN_SERVICE: &str = "NETWORK_TOKEN"; - -pub async fn mk_tokenization_req( +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn generate_network_token( state: &routes::SessionState, payload_bytes: &[u8], - customer_id: id_type::CustomerId, + customer_id: id_type::GlobalCustomerId, tokenization_service: &settings::NetworkTokenizationService, -) -> CustomResult<(CardNetworkTokenResponsePayload, Option), errors::NetworkTokenizationError> -{ +) -> CustomResult< + (domain::GenerateNetworkTokenResponsePayload, String), + errors::NetworkTokenizationError, +> { let enc_key = tokenization_service.public_key.peek().clone(); let key_id = tokenization_service.key_id.clone(); @@ -174,12 +173,12 @@ pub async fn mk_tokenization_req( .change_context(errors::NetworkTokenizationError::SaveNetworkTokenFailed) .attach_printable("Error on jwe encrypt")?; - let order_data = OrderData { + let order_data = domain::OrderData { consent_id: uuid::Uuid::new_v4().to_string(), customer_id, }; - let api_payload = ApiPayload { + let api_payload = domain::ApiPayload { service: NETWORK_TOKEN_SERVICE.to_string(), card_data: Secret::new(jwt), order_data, @@ -208,14 +207,14 @@ pub async fn mk_tokenization_req( let response = services::call_connector_api(state, request, "generate_token") .await - .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed); + .change_context(errors::NetworkTokenizationError::ApiError); let res = response .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) .attach_printable("Error while receiving response") .and_then(|inner| match inner { Err(err_res) => { - let parsed_error: NetworkTokenErrorResponse = err_res + let parsed_error: domain::NetworkTokenErrorResponse = err_res .response .parse_struct("Card Network Tokenization Response") .change_context( @@ -236,11 +235,11 @@ pub async fn mk_tokenization_req( logger::error!("Error while deserializing response: {:?}", err); })?; - let network_response: CardNetworkTokenResponse = res + let network_response: domain::CardNetworkTokenResponse = res .response .parse_struct("Card Network Tokenization Response") .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; - logger::debug!("Network Token Response: {:?}", network_response); //added for debugging, will be removed + logger::debug!("Network Token Response: {:?}", network_response); let dec_key = tokenization_service.private_key.peek().clone(); @@ -256,19 +255,25 @@ pub async fn mk_tokenization_req( "Failed to decrypt the tokenization response from the tokenization service", )?; - let cn_response: CardNetworkTokenResponsePayload = + let cn_response: domain::GenerateNetworkTokenResponsePayload = serde_json::from_str(&card_network_token_response) .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; - Ok((cn_response.clone(), Some(cn_response.card_reference))) + Ok((cn_response.clone(), cn_response.card_reference)) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn make_card_network_tokenization_request( state: &routes::SessionState, card: &domain::Card, customer_id: &id_type::CustomerId, -) -> CustomResult<(CardNetworkTokenResponsePayload, Option), errors::NetworkTokenizationError> -{ - let card_data = CardData { +) -> CustomResult< + (domain::CardNetworkTokenResponsePayload, Option), + errors::NetworkTokenizationError, +> { + let card_data = domain::CardData { card_number: card.card_number.clone(), exp_month: card.card_exp_month.clone(), exp_year: card.card_exp_year.clone(), @@ -308,18 +313,74 @@ pub async fn make_card_network_tokenization_request( } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn make_card_network_tokenization_request( + state: &routes::SessionState, + card: &api_payment_methods::CardDetail, + customer_id: &id_type::GlobalCustomerId, +) -> CustomResult<(NetworkTokenDetails, String), errors::NetworkTokenizationError> { + let card_data = domain::CardData { + card_number: card.card_number.clone(), + exp_month: card.card_exp_month.clone(), + exp_year: card.card_exp_year.clone(), + card_security_code: None, + }; + + let payload = card_data + .encode_to_string_of_json() + .and_then(|x| x.encode_to_string_of_json()) + .change_context(errors::NetworkTokenizationError::RequestEncodingFailed)?; + + let payload_bytes = payload.as_bytes(); + let network_tokenization_service = match &state.conf.network_tokenization_service { + Some(nt_service) => Ok(nt_service.get_inner()), + None => Err(report!( + errors::NetworkTokenizationError::NetworkTokenizationServiceNotConfigured + )), + }?; + + let (resp, network_token_req_ref_id) = record_operation_time( + async { + generate_network_token( + state, + payload_bytes, + customer_id.clone(), + network_tokenization_service, + ) + .await + .inspect_err(|e| logger::error!(error=?e, "Error while making tokenization request")) + }, + &metrics::GENERATE_NETWORK_TOKEN_TIME, + router_env::metric_attributes!(("locker", "rust")), + ) + .await?; + + let network_token_details = NetworkTokenDetails { + network_token: resp.token, + network_token_exp_month: resp.token_expiry_month, + network_token_exp_year: resp.token_expiry_year, + card_issuer: card.card_issuer.clone(), + card_network: Some(resp.card_brand), + card_type: card.card_type.clone(), + card_issuing_country: card.card_issuing_country, + card_holder_name: card.card_holder_name.clone(), + nick_name: card.nick_name.clone(), + }; + Ok((network_token_details, network_token_req_ref_id)) +} + #[cfg(feature = "v1")] pub async fn get_network_token( state: &routes::SessionState, customer_id: id_type::CustomerId, network_token_requestor_ref_id: String, tokenization_service: &settings::NetworkTokenizationService, -) -> CustomResult { +) -> CustomResult { let mut request = services::Request::new( services::Method::Post, tokenization_service.fetch_token_url.as_str(), ); - let payload = GetCardToken { + let payload = domain::GetCardToken { card_reference: network_token_requestor_ref_id, customer_id, }; @@ -342,14 +403,14 @@ pub async fn get_network_token( // Send the request using `call_connector_api` let response = services::call_connector_api(state, request, "get network token") .await - .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed); + .change_context(errors::NetworkTokenizationError::ApiError); let res = response .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) .attach_printable("Error while receiving response") .and_then(|inner| match inner { Err(err_res) => { - let parsed_error: NetworkTokenErrorResponse = err_res + let parsed_error: domain::NetworkTokenErrorResponse = err_res .response .parse_struct("Card Network Tokenization Response") .change_context( @@ -367,7 +428,75 @@ pub async fn get_network_token( Ok(res) => Ok(res), })?; - let token_response: TokenResponse = res + let token_response: domain::TokenResponse = res + .response + .parse_struct("Get Network Token Response") + .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; + logger::info!("Fetch Network Token Response: {:?}", token_response); + + Ok(token_response) +} + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn get_network_token( + state: &routes::SessionState, + customer_id: &id_type::GlobalCustomerId, + network_token_requestor_ref_id: String, + tokenization_service: &settings::NetworkTokenizationService, +) -> CustomResult { + let mut request = services::Request::new( + services::Method::Post, + tokenization_service.fetch_token_url.as_str(), + ); + let payload = domain::GetCardToken { + card_reference: network_token_requestor_ref_id, + customer_id: customer_id.clone(), + }; + + request.add_header(headers::CONTENT_TYPE, "application/json".into()); + request.add_header( + headers::AUTHORIZATION, + tokenization_service + .token_service_api_key + .clone() + .peek() + .clone() + .into_masked(), + ); + request.add_default_headers(); + request.set_body(RequestContent::Json(Box::new(payload))); + + logger::info!("Request to fetch network token: {:?}", request); + + // Send the request using `call_connector_api` + let response = services::call_connector_api(state, request, "get network token") + .await + .change_context(errors::NetworkTokenizationError::ApiError); + + let res = response + .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) + .attach_printable("Error while receiving response") + .and_then(|inner| match inner { + Err(err_res) => { + let parsed_error: domain::NetworkTokenErrorResponse = err_res + .response + .parse_struct("Card Network Tokenization Response") + .change_context( + errors::NetworkTokenizationError::ResponseDeserializationFailed, + )?; + logger::error!( + error_code = %parsed_error.error_info.code, + developer_message = %parsed_error.error_info.developer_message, + "Network tokenization error: {}", + parsed_error.error_message + ); + Err(errors::NetworkTokenizationError::ResponseDeserializationFailed) + .attach_printable(format!("Response Deserialization Failed: {err_res:?}")) + } + Ok(res) => Ok(res), + })?; + + let token_response: domain::TokenResponse = res .response .parse_struct("Get Network Token Response") .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; @@ -415,9 +544,11 @@ pub async fn get_token_from_tokenization_service( .network_token_payment_method_data .clone() .map(|x| x.into_inner().expose()) - .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|v| serde_json::from_value::(v).ok()) .and_then(|pmd| match pmd { - PaymentMethodsData::Card(token) => Some(api::CardDetailFromLocker::from(token)), + api_payment_methods::PaymentMethodsData::Card(token) => { + Some(api::CardDetailFromLocker::from(token)) + } _ => None, }) .ok_or(errors::ApiErrorResponse::InternalServerError) @@ -452,9 +583,11 @@ pub async fn do_status_check_for_network_token( .network_token_payment_method_data .clone() .map(|x| x.into_inner().expose()) - .and_then(|v| serde_json::from_value::(v).ok()) + .and_then(|v| serde_json::from_value::(v).ok()) .and_then(|pmd| match pmd { - PaymentMethodsData::Card(token) => Some(api::CardDetailFromLocker::from(token)), + api_payment_methods::PaymentMethodsData::Card(token) => { + Some(api::CardDetailFromLocker::from(token)) + } _ => None, }); let network_token_requestor_reference_id = payment_method_info @@ -507,6 +640,10 @@ pub async fn do_status_check_for_network_token( } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn check_token_status_with_tokenization_service( state: &routes::SessionState, customer_id: &id_type::CustomerId, @@ -518,7 +655,7 @@ pub async fn check_token_status_with_tokenization_service( services::Method::Post, tokenization_service.check_token_status_url.as_str(), ); - let payload = CheckTokenStatus { + let payload = domain::CheckTokenStatus { card_reference: network_token_requestor_reference_id, customer_id: customer_id.clone(), }; @@ -539,13 +676,13 @@ pub async fn check_token_status_with_tokenization_service( // Send the request using `call_connector_api` let response = services::call_connector_api(state, request, "Check Network token Status") .await - .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed); + .change_context(errors::NetworkTokenizationError::ApiError); let res = response .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) .attach_printable("Error while receiving response") .and_then(|inner| match inner { Err(err_res) => { - let parsed_error: NetworkTokenErrorResponse = err_res + let parsed_error: domain::NetworkTokenErrorResponse = err_res .response .parse_struct("Delete Network Tokenization Response") .change_context( @@ -566,20 +703,35 @@ pub async fn check_token_status_with_tokenization_service( logger::error!("Error while deserializing response: {:?}", err); })?; - let check_token_status_response: CheckTokenStatusResponse = res + let check_token_status_response: domain::CheckTokenStatusResponse = res .response .parse_struct("Delete Network Tokenization Response") .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; match check_token_status_response.payload.token_status { - TokenStatus::Active => Ok(( + domain::TokenStatus::Active => Ok(( Some(check_token_status_response.payload.token_expiry_month), Some(check_token_status_response.payload.token_expiry_year), )), - TokenStatus::Inactive => Ok((None, None)), + domain::TokenStatus::Inactive => Ok((None, None)), } } +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn check_token_status_with_tokenization_service( + _state: &routes::SessionState, + _customer_id: &id_type::GlobalCustomerId, + _network_token_requestor_reference_id: String, + _tokenization_service: &settings::NetworkTokenizationService, +) -> CustomResult<(Option>, Option>), errors::NetworkTokenizationError> +{ + todo!() +} + +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn delete_network_token_from_locker_and_token_service( state: &routes::SessionState, customer_id: &id_type::CustomerId, @@ -624,6 +776,10 @@ pub async fn delete_network_token_from_locker_and_token_service( Ok(resp) } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] pub async fn delete_network_token_from_tokenization_service( state: &routes::SessionState, network_token_requestor_reference_id: String, @@ -634,7 +790,7 @@ pub async fn delete_network_token_from_tokenization_service( services::Method::Post, tokenization_service.delete_token_url.as_str(), ); - let payload = DeleteCardToken { + let payload = domain::DeleteCardToken { card_reference: network_token_requestor_reference_id, customer_id: customer_id.clone(), }; @@ -657,13 +813,13 @@ pub async fn delete_network_token_from_tokenization_service( // Send the request using `call_connector_api` let response = services::call_connector_api(state, request, "delete network token") .await - .change_context(errors::NetworkTokenizationError::DeleteNetworkTokenFailed); + .change_context(errors::NetworkTokenizationError::ApiError); let res = response .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed) .attach_printable("Error while receiving response") .and_then(|inner| match inner { Err(err_res) => { - let parsed_error: NetworkTokenErrorResponse = err_res + let parsed_error: domain::NetworkTokenErrorResponse = err_res .response .parse_struct("Delete Network Tokenization Response") .change_context( @@ -684,17 +840,29 @@ pub async fn delete_network_token_from_tokenization_service( logger::error!("Error while deserializing response: {:?}", err); })?; - let delete_token_response: DeleteNetworkTokenResponse = res + let delete_token_response: domain::DeleteNetworkTokenResponse = res .response .parse_struct("Delete Network Tokenization Response") .change_context(errors::NetworkTokenizationError::ResponseDeserializationFailed)?; logger::info!("Delete Network Token Response: {:?}", delete_token_response); - if delete_token_response.status == DeleteNetworkTokenStatus::Success { + if delete_token_response.status == domain::DeleteNetworkTokenStatus::Success { Ok(true) } else { Err(errors::NetworkTokenizationError::DeleteNetworkTokenFailed) .attach_printable("Delete Token at Token service failed") } } + +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +pub async fn delete_network_token_from_locker_and_token_service( + _state: &routes::SessionState, + _customer_id: &id_type::GlobalCustomerId, + _merchant_id: &id_type::MerchantId, + _payment_method_id: String, + _network_token_locker_id: Option, + _network_token_requestor_reference_id: String, +) -> errors::RouterResult { + todo!() +} diff --git a/crates/router/src/core/payment_methods/transformers.rs b/crates/router/src/core/payment_methods/transformers.rs index c4d3eafe463..947ae97b119 100644 --- a/crates/router/src/core/payment_methods/transformers.rs +++ b/crates/router/src/core/payment_methods/transformers.rs @@ -545,6 +545,7 @@ pub fn generate_pm_vaulting_req_from_update_request( card_holder_name: update_card.card_holder_name, nick_name: update_card.nick_name, }), + _ => todo!(), //todo! - since support for network tokenization is not added PaymentMethodUpdateData. should be handled later. } } diff --git a/crates/router/src/routes/payment_methods.rs b/crates/router/src/routes/payment_methods.rs index 0b6246d4483..036646f719f 100644 --- a/crates/router/src/routes/payment_methods.rs +++ b/crates/router/src/routes/payment_methods.rs @@ -85,6 +85,7 @@ pub async fn create_payment_method_api( req, &auth.merchant_account, &auth.key_store, + &auth.profile, )) .await }, diff --git a/crates/router/src/types/domain.rs b/crates/router/src/types/domain.rs index 9a87b6d28be..270ed3ca64d 100644 --- a/crates/router/src/types/domain.rs +++ b/crates/router/src/types/domain.rs @@ -20,6 +20,10 @@ mod callback_mapper { pub use hyperswitch_domain_models::callback_mapper::CallbackMapper; } +mod network_tokenization { + pub use hyperswitch_domain_models::network_tokenization::*; +} + pub use customers::*; pub use merchant_account::*; @@ -48,6 +52,7 @@ pub use consts::*; pub use event::*; pub use merchant_connector_account::*; pub use merchant_key_store::*; +pub use network_tokenization::*; pub use payment_methods::*; pub use payments::*; #[cfg(feature = "olap")] diff --git a/crates/router/src/types/payment_methods.rs b/crates/router/src/types/payment_methods.rs index c40d6fedfe7..d639bc44bc8 100644 --- a/crates/router/src/types/payment_methods.rs +++ b/crates/router/src/types/payment_methods.rs @@ -1,6 +1,8 @@ #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use common_utils::generate_id; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] +use hyperswitch_domain_models::payment_method_data::NetworkTokenDetails; +#[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use masking::Secret; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -115,6 +117,7 @@ impl VaultingInterface for VaultDelete { #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] pub enum PaymentMethodVaultingData { Card(api::CardDetail), + NetworkToken(NetworkTokenDetails), } #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] @@ -122,6 +125,7 @@ impl VaultingDataInterface for PaymentMethodVaultingData { fn get_vaulting_data_key(&self) -> String { match &self { Self::Card(card) => card.card_number.to_string(), + Self::NetworkToken(network_token) => network_token.network_token.to_string(), } } } From 0ba4ccfc8b38a918a56eab66715005b4c448172b Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:11:06 +0530 Subject: [PATCH 101/133] feat(core): introduce accounts schema for accounts related tables (#7113) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/config.example.toml | 1 + config/deployments/env_specific.toml | 1 + config/development.toml | 1 + config/docker_compose.toml | 3 +- crates/diesel_models/src/organization.rs | 4 +- crates/drainer/src/settings.rs | 3 + crates/router/src/configs/settings.rs | 108 +++++++++++++++++- crates/router/src/connection.rs | 40 +++++++ crates/router/src/core/admin.rs | 16 +-- crates/router/src/core/user.rs | 6 +- crates/router/src/core/user_role.rs | 2 +- crates/router/src/db.rs | 24 +++- crates/router/src/db/kafka_store.rs | 7 +- crates/router/src/db/organization.rs | 6 +- crates/router/src/routes/app.rs | 48 +++++--- crates/router/src/routes/payments.rs | 12 +- crates/router/src/types/domain/user.rs | 2 +- crates/router/src/utils/user/theme.rs | 2 +- crates/storage_impl/src/config.rs | 4 +- crates/storage_impl/src/database/store.rs | 44 +++++++ crates/storage_impl/src/lib.rs | 16 +++ loadtest/config/development.toml | 3 +- .../down.sql | 3 + .../up.sql | 3 + .../down.sql | 3 - .../up.sql | 2 - 26 files changed, 308 insertions(+), 56 deletions(-) create mode 100644 v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/down.sql create mode 100644 v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/up.sql diff --git a/config/config.example.toml b/config/config.example.toml index 64b382eebae..17b0d5b8c7b 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -778,6 +778,7 @@ global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" [multitenancy.tenants.public] base_url = "http://localhost:8080" # URL of the tenant schema = "public" # Postgres db schema +accounts_schema = "public" redis_key_prefix = "" # Redis key distinguisher clickhouse_database = "default" # Clickhouse database diff --git a/config/deployments/env_specific.toml b/config/deployments/env_specific.toml index 4e5fa3dba44..157e0aa3a42 100644 --- a/config/deployments/env_specific.toml +++ b/config/deployments/env_specific.toml @@ -308,6 +308,7 @@ global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" [multitenancy.tenants.public] base_url = "http://localhost:8080" schema = "public" +accounts_schema = "public" redis_key_prefix = "" clickhouse_database = "default" diff --git a/config/development.toml b/config/development.toml index 7c4af97971f..153c8a996de 100644 --- a/config/development.toml +++ b/config/development.toml @@ -860,6 +860,7 @@ global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "g [multitenancy.tenants.public] base_url = "http://localhost:8080" schema = "public" +accounts_schema = "public" redis_key_prefix = "" clickhouse_database = "default" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 48df4c2c93b..48581fbea06 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -649,7 +649,8 @@ global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" [multitenancy.tenants.public] base_url = "http://localhost:8080" -schema = "public" +schema = "public" +accounts_schema = "public" redis_key_prefix = "" clickhouse_database = "default" diff --git a/crates/diesel_models/src/organization.rs b/crates/diesel_models/src/organization.rs index 6a3cad24e1c..753f1cf3c30 100644 --- a/crates/diesel_models/src/organization.rs +++ b/crates/diesel_models/src/organization.rs @@ -100,7 +100,7 @@ impl Organization { pub struct OrganizationNew { org_id: id_type::OrganizationId, org_name: Option, - id: Option, + id: id_type::OrganizationId, organization_name: Option, pub organization_details: Option, pub metadata: Option, @@ -126,7 +126,7 @@ impl OrganizationNew { Self { org_id: id.clone(), org_name: organization_name.clone(), - id: Some(id), + id, organization_name, organization_details: None, metadata: None, diff --git a/crates/drainer/src/settings.rs b/crates/drainer/src/settings.rs index 9b6c88b3466..ea926f32afa 100644 --- a/crates/drainer/src/settings.rs +++ b/crates/drainer/src/settings.rs @@ -146,6 +146,7 @@ impl<'de> Deserialize<'de> for TenantConfig { struct Inner { base_url: String, schema: String, + accounts_schema: String, redis_key_prefix: String, clickhouse_database: String, } @@ -162,6 +163,7 @@ impl<'de> Deserialize<'de> for TenantConfig { tenant_id: key, base_url: value.base_url, schema: value.schema, + accounts_schema: value.accounts_schema, redis_key_prefix: value.redis_key_prefix, clickhouse_database: value.clickhouse_database, }, @@ -177,6 +179,7 @@ pub struct Tenant { pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, + pub accounts_schema: String, pub redis_key_prefix: String, pub clickhouse_database: String, } diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 5445cb8697f..91ef78a68d5 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -1,6 +1,7 @@ use std::{ collections::{HashMap, HashSet}, path::PathBuf, + sync::Arc, }; #[cfg(feature = "olap")] @@ -32,11 +33,14 @@ use serde::Deserialize; use storage_impl::config::QueueStrategy; #[cfg(feature = "olap")] -use crate::analytics::AnalyticsConfig; +use crate::analytics::{AnalyticsConfig, AnalyticsProvider}; use crate::{ + configs, core::errors::{ApplicationError, ApplicationResult}, env::{self, Env}, events::EventsConfig, + routes::app, + AppState, }; pub const REQUIRED_FIELDS_CONFIG_FILE: &str = "payment_required_fields_v2.toml"; @@ -171,11 +175,96 @@ pub struct DecisionConfig { #[derive(Debug, Clone, Default)] pub struct TenantConfig(pub HashMap); +impl TenantConfig { + /// # Panics + /// + /// Panics if Failed to create event handler + pub async fn get_store_interface_map( + &self, + storage_impl: &app::StorageImpl, + conf: &configs::Settings, + cache_store: Arc, + testable: bool, + ) -> HashMap> { + #[allow(clippy::expect_used)] + let event_handler = conf + .events + .get_event_handler() + .await + .expect("Failed to create event handler"); + futures::future::join_all(self.0.iter().map(|(tenant_name, tenant)| async { + let store = AppState::get_store_interface( + storage_impl, + &event_handler, + conf, + tenant, + cache_store.clone(), + testable, + ) + .await + .get_storage_interface(); + (tenant_name.clone(), store) + })) + .await + .into_iter() + .collect() + } + /// # Panics + /// + /// Panics if Failed to create event handler + pub async fn get_accounts_store_interface_map( + &self, + storage_impl: &app::StorageImpl, + conf: &configs::Settings, + cache_store: Arc, + testable: bool, + ) -> HashMap> { + #[allow(clippy::expect_used)] + let event_handler = conf + .events + .get_event_handler() + .await + .expect("Failed to create event handler"); + futures::future::join_all(self.0.iter().map(|(tenant_name, tenant)| async { + let store = AppState::get_store_interface( + storage_impl, + &event_handler, + conf, + tenant, + cache_store.clone(), + testable, + ) + .await + .get_accounts_storage_interface(); + (tenant_name.clone(), store) + })) + .await + .into_iter() + .collect() + } + #[cfg(feature = "olap")] + pub async fn get_pools_map( + &self, + analytics_config: &AnalyticsConfig, + ) -> HashMap { + futures::future::join_all(self.0.iter().map(|(tenant_name, tenant)| async { + ( + tenant_name.clone(), + AnalyticsProvider::from_conf(analytics_config, tenant).await, + ) + })) + .await + .into_iter() + .collect() + } +} + #[derive(Debug, Clone)] pub struct Tenant { pub tenant_id: id_type::TenantId, pub base_url: String, pub schema: String, + pub accounts_schema: String, pub redis_key_prefix: String, pub clickhouse_database: String, pub user: TenantUserConfig, @@ -187,6 +276,12 @@ pub struct TenantUserConfig { } impl storage_impl::config::TenantConfig for Tenant { + fn get_tenant_id(&self) -> &id_type::TenantId { + &self.tenant_id + } + fn get_accounts_schema(&self) -> &str { + self.accounts_schema.as_str() + } fn get_schema(&self) -> &str { self.schema.as_str() } @@ -198,6 +293,7 @@ impl storage_impl::config::TenantConfig for Tenant { } } +// Todo: Global tenant should not be part of tenant config(https://github.com/juspay/hyperswitch/issues/7237) #[derive(Debug, Deserialize, Clone)] pub struct GlobalTenant { #[serde(default = "id_type::TenantId::get_default_global_tenant_id")] @@ -206,8 +302,14 @@ pub struct GlobalTenant { pub redis_key_prefix: String, pub clickhouse_database: String, } - +// Todo: Global tenant should not be part of tenant config impl storage_impl::config::TenantConfig for GlobalTenant { + fn get_tenant_id(&self) -> &id_type::TenantId { + &self.tenant_id + } + fn get_accounts_schema(&self) -> &str { + self.schema.as_str() + } fn get_schema(&self) -> &str { self.schema.as_str() } @@ -1169,6 +1271,7 @@ impl<'de> Deserialize<'de> for TenantConfig { struct Inner { base_url: String, schema: String, + accounts_schema: String, redis_key_prefix: String, clickhouse_database: String, user: TenantUserConfig, @@ -1186,6 +1289,7 @@ impl<'de> Deserialize<'de> for TenantConfig { tenant_id: key, base_url: value.base_url, schema: value.schema, + accounts_schema: value.accounts_schema, redis_key_prefix: value.redis_key_prefix, clickhouse_database: value.clickhouse_database, user: value.user, diff --git a/crates/router/src/connection.rs b/crates/router/src/connection.rs index 009149b7225..818dab44115 100644 --- a/crates/router/src/connection.rs +++ b/crates/router/src/connection.rs @@ -48,6 +48,32 @@ pub async fn pg_connection_read( .change_context(storage_errors::StorageError::DatabaseConnectionError) } +pub async fn pg_accounts_connection_read( + store: &T, +) -> errors::CustomResult< + PooledConnection<'_, async_bb8_diesel::ConnectionManager>, + storage_errors::StorageError, +> { + // If only OLAP is enabled get replica pool. + #[cfg(all(feature = "olap", not(feature = "oltp")))] + let pool = store.get_accounts_replica_pool(); + + // If either one of these are true we need to get master pool. + // 1. Only OLTP is enabled. + // 2. Both OLAP and OLTP is enabled. + // 3. Both OLAP and OLTP is disabled. + #[cfg(any( + all(not(feature = "olap"), feature = "oltp"), + all(feature = "olap", feature = "oltp"), + all(not(feature = "olap"), not(feature = "oltp")) + ))] + let pool = store.get_accounts_master_pool(); + + pool.get() + .await + .change_context(storage_errors::StorageError::DatabaseConnectionError) +} + pub async fn pg_connection_write( store: &T, ) -> errors::CustomResult< @@ -61,3 +87,17 @@ pub async fn pg_connection_write( .await .change_context(storage_errors::StorageError::DatabaseConnectionError) } + +pub async fn pg_accounts_connection_write( + store: &T, +) -> errors::CustomResult< + PooledConnection<'_, async_bb8_diesel::ConnectionManager>, + storage_errors::StorageError, +> { + // Since all writes should happen to master DB only choose master DB. + let pool = store.get_accounts_master_pool(); + + pool.get() + .await + .change_context(storage_errors::StorageError::DatabaseConnectionError) +} diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 0e915dd7708..958dbf74382 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -34,7 +34,7 @@ use crate::{ pm_auth::helpers::PaymentAuthConnectorDataExt, routing, utils as core_utils, }, - db::StorageInterface, + db::{AccountsStorageInterface, StorageInterface}, routes::{metrics, SessionState}, services::{ self, @@ -120,7 +120,7 @@ pub async fn create_organization( ) -> RouterResponse { let db_organization = ForeignFrom::foreign_from(req); state - .store + .accounts_store .insert_organization(db_organization) .await .to_duplicate_response(errors::ApiErrorResponse::GenericDuplicateError { @@ -143,7 +143,7 @@ pub async fn update_organization( metadata: req.metadata, }; state - .store + .accounts_store .update_organization_by_org_id(&org_id.organization_id, organization_update) .await .to_not_found_response(errors::ApiErrorResponse::GenericNotFoundError { @@ -165,7 +165,7 @@ pub async fn get_organization( #[cfg(all(feature = "v1", feature = "olap"))] { CreateOrValidateOrganization::new(Some(org_id.organization_id)) - .create_or_validate(state.store.as_ref()) + .create_or_validate(state.accounts_store.as_ref()) .await .map(ForeignFrom::foreign_from) .map(service_api::ApplicationResponse::Json) @@ -173,7 +173,7 @@ pub async fn get_organization( #[cfg(all(feature = "v2", feature = "olap"))] { CreateOrValidateOrganization::new(org_id.organization_id) - .create_or_validate(state.store.as_ref()) + .create_or_validate(state.accounts_store.as_ref()) .await .map(ForeignFrom::foreign_from) .map(service_api::ApplicationResponse::Json) @@ -283,7 +283,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { key_store: domain::MerchantKeyStore, identifier: &id_type::MerchantId, ) -> RouterResult { - let db = &*state.store; + let db = &*state.accounts_store; let publishable_key = create_merchant_publishable_key(); let primary_business_details = self.get_primary_details_as_value().change_context( @@ -454,7 +454,7 @@ impl CreateOrValidateOrganization { /// Apply the action, whether to create the organization or validate the given organization_id async fn create_or_validate( &self, - db: &dyn StorageInterface, + db: &dyn AccountsStorageInterface, ) -> RouterResult { match self { #[cfg(feature = "v1")] @@ -609,7 +609,7 @@ impl MerchantAccountCreateBridge for api::MerchantAccountCreate { identifier: &id_type::MerchantId, ) -> RouterResult { let publishable_key = create_merchant_publishable_key(); - let db = &*state.store; + let db = &*state.accounts_store; let metadata = self.get_metadata_as_secret().change_context( errors::ApiErrorResponse::InvalidDataValue { diff --git a/crates/router/src/core/user.rs b/crates/router/src/core/user.rs index ead0d4cd572..4f19f040bdb 100644 --- a/crates/router/src/core/user.rs +++ b/crates/router/src/core/user.rs @@ -1462,7 +1462,7 @@ pub async fn create_org_merchant_for_user( ) -> UserResponse<()> { let db_organization = ForeignFrom::foreign_from(req.clone()); let org: diesel_models::organization::Organization = state - .store + .accounts_store .insert_organization(db_organization) .await .change_context(UserErrors::InternalServerError)?; @@ -1548,7 +1548,7 @@ pub async fn list_user_roles_details( .collect::>(); let org_name = state - .store + .accounts_store .find_organization_by_org_id(&user_from_token.org_id) .await .change_context(UserErrors::InternalServerError) @@ -2837,7 +2837,7 @@ pub async fn list_orgs_for_user( let resp = futures::future::try_join_all( orgs.iter() - .map(|org_id| state.store.find_organization_by_org_id(org_id)), + .map(|org_id| state.accounts_store.find_organization_by_org_id(org_id)), ) .await .change_context(UserErrors::InternalServerError)? diff --git a/crates/router/src/core/user_role.rs b/crates/router/src/core/user_role.rs index 2b09a4ae274..0ece98da135 100644 --- a/crates/router/src/core/user_role.rs +++ b/crates/router/src/core/user_role.rs @@ -988,7 +988,7 @@ pub async fn list_invitations_for_user( let org_name_map = futures::future::try_join_all(org_ids.into_iter().map(|org_id| async { let org_name = state - .store + .accounts_store .find_organization_by_org_id(&org_id) .await .change_context(UserErrors::InternalServerError)? diff --git a/crates/router/src/db.rs b/crates/router/src/db.rs index 17da4902bac..70bc533f9a7 100644 --- a/crates/router/src/db.rs +++ b/crates/router/src/db.rs @@ -126,7 +126,6 @@ pub trait StorageInterface: + RedisConnInterface + RequestIdStore + business_profile::ProfileInterface - + OrganizationInterface + routing_algorithm::RoutingAlgorithmInterface + gsm::GsmInterface + unified_translations::UnifiedTranslationsInterface @@ -159,9 +158,18 @@ pub trait GlobalStorageInterface: { } -pub trait CommonStorageInterface: StorageInterface + GlobalStorageInterface { +#[async_trait::async_trait] +pub trait AccountsStorageInterface: + Send + Sync + dyn_clone::DynClone + OrganizationInterface + 'static +{ +} + +pub trait CommonStorageInterface: + StorageInterface + GlobalStorageInterface + AccountsStorageInterface +{ fn get_storage_interface(&self) -> Box; fn get_global_storage_interface(&self) -> Box; + fn get_accounts_storage_interface(&self) -> Box; } pub trait MasterKeyInterface { @@ -198,6 +206,8 @@ impl StorageInterface for Store { #[async_trait::async_trait] impl GlobalStorageInterface for Store {} +impl AccountsStorageInterface for Store {} + #[async_trait::async_trait] impl StorageInterface for MockDb { fn get_scheduler_db(&self) -> Box { @@ -212,6 +222,8 @@ impl StorageInterface for MockDb { #[async_trait::async_trait] impl GlobalStorageInterface for MockDb {} +impl AccountsStorageInterface for MockDb {} + impl CommonStorageInterface for MockDb { fn get_global_storage_interface(&self) -> Box { Box::new(self.clone()) @@ -219,6 +231,10 @@ impl CommonStorageInterface for MockDb { fn get_storage_interface(&self) -> Box { Box::new(self.clone()) } + + fn get_accounts_storage_interface(&self) -> Box { + Box::new(self.clone()) + } } impl CommonStorageInterface for Store { @@ -228,6 +244,9 @@ impl CommonStorageInterface for Store { fn get_storage_interface(&self) -> Box { Box::new(self.clone()) } + fn get_accounts_storage_interface(&self) -> Box { + Box::new(self.clone()) + } } pub trait RequestIdStore { @@ -267,6 +286,7 @@ where dyn_clone::clone_trait_object!(StorageInterface); dyn_clone::clone_trait_object!(GlobalStorageInterface); +dyn_clone::clone_trait_object!(AccountsStorageInterface); impl RequestIdStore for KafkaStore { fn add_request_id(&mut self, request_id: String) { diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index 5d961317a1c..d4a1fee0929 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -80,7 +80,8 @@ use crate::{ reverse_lookup::ReverseLookupInterface, routing_algorithm::RoutingAlgorithmInterface, unified_translations::UnifiedTranslationsInterface, - CommonStorageInterface, GlobalStorageInterface, MasterKeyInterface, StorageInterface, + AccountsStorageInterface, CommonStorageInterface, GlobalStorageInterface, + MasterKeyInterface, StorageInterface, }, services::{kafka::KafkaProducer, Store}, types::{domain, storage, AccessToken}, @@ -3095,6 +3096,7 @@ impl StorageInterface for KafkaStore { } impl GlobalStorageInterface for KafkaStore {} +impl AccountsStorageInterface for KafkaStore {} impl CommonStorageInterface for KafkaStore { fn get_storage_interface(&self) -> Box { @@ -3103,6 +3105,9 @@ impl CommonStorageInterface for KafkaStore { fn get_global_storage_interface(&self) -> Box { Box::new(self.clone()) } + fn get_accounts_storage_interface(&self) -> Box { + Box::new(self.clone()) + } } #[async_trait::async_trait] diff --git a/crates/router/src/db/organization.rs b/crates/router/src/db/organization.rs index bc79fdc0a06..b1dc58f0402 100644 --- a/crates/router/src/db/organization.rs +++ b/crates/router/src/db/organization.rs @@ -31,7 +31,7 @@ impl OrganizationInterface for Store { &self, organization: storage::OrganizationNew, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_accounts_connection_write(self).await?; organization .insert(&conn) .await @@ -43,7 +43,7 @@ impl OrganizationInterface for Store { &self, org_id: &id_type::OrganizationId, ) -> CustomResult { - let conn = connection::pg_connection_read(self).await?; + let conn = connection::pg_accounts_connection_read(self).await?; storage::Organization::find_by_org_id(&conn, org_id.to_owned()) .await .map_err(|error| report!(errors::StorageError::from(error))) @@ -55,7 +55,7 @@ impl OrganizationInterface for Store { org_id: &id_type::OrganizationId, update: storage::OrganizationUpdate, ) -> CustomResult { - let conn = connection::pg_connection_write(self).await?; + let conn = connection::pg_accounts_connection_write(self).await?; storage::Organization::update_by_org_id(&conn, org_id.to_owned(), update) .await diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index c5e1f8a1082..ee1e06af939 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -78,7 +78,10 @@ use crate::routes::fraud_check as frm_routes; use crate::routes::recon as recon_routes; pub use crate::{ configs::settings, - db::{CommonStorageInterface, GlobalStorageInterface, StorageImpl, StorageInterface}, + db::{ + AccountsStorageInterface, CommonStorageInterface, GlobalStorageInterface, StorageImpl, + StorageInterface, + }, events::EventsHandler, services::{get_cache_store, get_store}, }; @@ -97,6 +100,7 @@ pub struct SessionState { pub store: Box, /// Global store is used for global schema operations in tables like Users and Tenants pub global_store: Box, + pub accounts_store: Box, pub conf: Arc>, pub api_client: Box, pub event_handler: EventsHandler, @@ -203,6 +207,8 @@ impl SessionStateInfo for SessionState { pub struct AppState { pub flow_name: String, pub global_store: Box, + // TODO: use a separate schema for accounts_store + pub accounts_store: HashMap>, pub stores: HashMap>, pub conf: Arc>, pub event_handler: EventsHandler, @@ -338,9 +344,6 @@ impl AppState { .expect("Failed to create opensearch client"), ); - #[cfg(feature = "olap")] - let mut pools: HashMap = HashMap::new(); - let mut stores = HashMap::new(); #[allow(clippy::expect_used)] let cache_store = get_cache_store(&conf.clone(), shut_down_signal, testable) .await @@ -355,23 +358,27 @@ impl AppState { ) .await .get_global_storage_interface(); - for (tenant_name, tenant) in conf.clone().multitenancy.get_tenants() { - let store: Box = Self::get_store_interface( + #[cfg(feature = "olap")] + let pools = conf + .multitenancy + .tenants + .get_pools_map(conf.analytics.get_inner()) + .await; + let stores = conf + .multitenancy + .tenants + .get_store_interface_map(&storage_impl, &conf, Arc::clone(&cache_store), testable) + .await; + let accounts_store = conf + .multitenancy + .tenants + .get_accounts_store_interface_map( &storage_impl, - &event_handler, &conf, - tenant, Arc::clone(&cache_store), testable, ) - .await - .get_storage_interface(); - stores.insert(tenant_name.clone(), store); - #[cfg(feature = "olap")] - let pool = AnalyticsProvider::from_conf(conf.analytics.get_inner(), tenant).await; - #[cfg(feature = "olap")] - pools.insert(tenant_name.clone(), pool); - } + .await; #[cfg(feature = "email")] let email_client = Arc::new(create_email_client(&conf).await); @@ -385,6 +392,7 @@ impl AppState { flow_name: String::from("default"), stores, global_store, + accounts_store, conf: Arc::new(conf), #[cfg(feature = "email")] email_client, @@ -404,7 +412,10 @@ impl AppState { .await } - async fn get_store_interface( + /// # Panics + /// + /// Panics if Failed to create store + pub async fn get_store_interface( storage_impl: &StorageImpl, event_handler: &EventsHandler, conf: &Settings, @@ -421,7 +432,7 @@ impl AppState { .await .expect("Failed to create store"), kafka_client.clone(), - TenantID(tenant.get_schema().to_string()), + TenantID(tenant.get_tenant_id().get_string_repr().to_owned()), tenant, ) .await, @@ -471,6 +482,7 @@ impl AppState { Ok(SessionState { store: self.stores.get(tenant).ok_or_else(err)?.clone(), global_store: self.global_store.clone(), + accounts_store: self.accounts_store.get(tenant).ok_or_else(err)?.clone(), conf: Arc::clone(&self.conf), api_client: self.api_client.clone(), event_handler, diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index a088f04b095..b55ab207f40 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -966,7 +966,7 @@ pub async fn payments_redirect_response( creds_identifier: None, }; let locking_action = payload.get_locking_input(flow.clone()); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -984,7 +984,7 @@ pub async fn payments_redirect_response( }, &auth::MerchantIdAuth(merchant_id), locking_action, - ) + )) .await } @@ -1016,7 +1016,7 @@ pub async fn payments_redirect_response_with_creds_identifier( }; let flow = Flow::PaymentsRedirect; let locking_action = payload.get_locking_input(flow.clone()); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -1034,7 +1034,7 @@ pub async fn payments_redirect_response_with_creds_identifier( }, &auth::MerchantIdAuth(merchant_id), locking_action, - ) + )) .await } @@ -1066,7 +1066,7 @@ pub async fn payments_complete_authorize_redirect( creds_identifier: None, }; let locking_action = payload.get_locking_input(flow.clone()); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -1085,7 +1085,7 @@ pub async fn payments_complete_authorize_redirect( }, &auth::MerchantIdAuth(merchant_id), locking_action, - ) + )) .await } diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index 569e7f9a99f..1eb2305058a 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -245,7 +245,7 @@ pub struct NewUserOrganization(diesel_org::OrganizationNew); impl NewUserOrganization { pub async fn insert_org_in_db(self, state: SessionState) -> UserResult { state - .store + .accounts_store .insert_organization(self.0) .await .map_err(|e| { diff --git a/crates/router/src/utils/user/theme.rs b/crates/router/src/utils/user/theme.rs index 9cc1c43462c..0b9d4e2829a 100644 --- a/crates/router/src/utils/user/theme.rs +++ b/crates/router/src/utils/user/theme.rs @@ -100,7 +100,7 @@ fn validate_tenant(state: &SessionState, tenant_id: &id_type::TenantId) -> UserR async fn validate_org(state: &SessionState, org_id: &id_type::OrganizationId) -> UserResult<()> { state - .store + .accounts_store .find_organization_by_org_id(org_id) .await .to_not_found_response(UserErrors::InvalidThemeLineage("org_id".to_string())) diff --git a/crates/storage_impl/src/config.rs b/crates/storage_impl/src/config.rs index ac68d1718a9..857c23161e0 100644 --- a/crates/storage_impl/src/config.rs +++ b/crates/storage_impl/src/config.rs @@ -1,4 +1,4 @@ -use common_utils::DbConnectionParams; +use common_utils::{id_type, DbConnectionParams}; use masking::Secret; #[derive(Debug, Clone, serde::Deserialize)] @@ -34,7 +34,9 @@ impl DbConnectionParams for Database { } pub trait TenantConfig: Send + Sync { + fn get_tenant_id(&self) -> &id_type::TenantId; fn get_schema(&self) -> &str; + fn get_accounts_schema(&self) -> &str; fn get_redis_key_prefix(&self) -> &str; fn get_clickhouse_database(&self) -> &str; } diff --git a/crates/storage_impl/src/database/store.rs b/crates/storage_impl/src/database/store.rs index 543cc47870d..547be83e3ae 100644 --- a/crates/storage_impl/src/database/store.rs +++ b/crates/storage_impl/src/database/store.rs @@ -20,11 +20,14 @@ pub trait DatabaseStore: Clone + Send + Sync { ) -> StorageResult; fn get_master_pool(&self) -> &PgPool; fn get_replica_pool(&self) -> &PgPool; + fn get_accounts_master_pool(&self) -> &PgPool; + fn get_accounts_replica_pool(&self) -> &PgPool; } #[derive(Debug, Clone)] pub struct Store { pub master_pool: PgPool, + pub accounts_pool: PgPool, } #[async_trait::async_trait] @@ -38,6 +41,12 @@ impl DatabaseStore for Store { Ok(Self { master_pool: diesel_make_pg_pool(&config, tenant_config.get_schema(), test_transaction) .await?, + accounts_pool: diesel_make_pg_pool( + &config, + tenant_config.get_accounts_schema(), + test_transaction, + ) + .await?, }) } @@ -48,12 +57,22 @@ impl DatabaseStore for Store { fn get_replica_pool(&self) -> &PgPool { &self.master_pool } + + fn get_accounts_master_pool(&self) -> &PgPool { + &self.accounts_pool + } + + fn get_accounts_replica_pool(&self) -> &PgPool { + &self.accounts_pool + } } #[derive(Debug, Clone)] pub struct ReplicaStore { pub master_pool: PgPool, pub replica_pool: PgPool, + pub accounts_master_pool: PgPool, + pub accounts_replica_pool: PgPool, } #[async_trait::async_trait] @@ -69,6 +88,13 @@ impl DatabaseStore for ReplicaStore { diesel_make_pg_pool(&master_config, tenant_config.get_schema(), test_transaction) .await .attach_printable("failed to create master pool")?; + let accounts_master_pool = diesel_make_pg_pool( + &master_config, + tenant_config.get_accounts_schema(), + test_transaction, + ) + .await + .attach_printable("failed to create accounts master pool")?; let replica_pool = diesel_make_pg_pool( &replica_config, tenant_config.get_schema(), @@ -76,9 +102,19 @@ impl DatabaseStore for ReplicaStore { ) .await .attach_printable("failed to create replica pool")?; + + let accounts_replica_pool = diesel_make_pg_pool( + &replica_config, + tenant_config.get_accounts_schema(), + test_transaction, + ) + .await + .attach_printable("failed to create accounts pool")?; Ok(Self { master_pool, replica_pool, + accounts_master_pool, + accounts_replica_pool, }) } @@ -89,6 +125,14 @@ impl DatabaseStore for ReplicaStore { fn get_replica_pool(&self) -> &PgPool { &self.replica_pool } + + fn get_accounts_master_pool(&self) -> &PgPool { + &self.accounts_master_pool + } + + fn get_accounts_replica_pool(&self) -> &PgPool { + &self.accounts_replica_pool + } } pub async fn diesel_make_pg_pool( diff --git a/crates/storage_impl/src/lib.rs b/crates/storage_impl/src/lib.rs index 8a329010447..137cd2a5d4a 100644 --- a/crates/storage_impl/src/lib.rs +++ b/crates/storage_impl/src/lib.rs @@ -86,6 +86,14 @@ where fn get_replica_pool(&self) -> &PgPool { self.db_store.get_replica_pool() } + + fn get_accounts_master_pool(&self) -> &PgPool { + self.db_store.get_accounts_master_pool() + } + + fn get_accounts_replica_pool(&self) -> &PgPool { + self.db_store.get_accounts_replica_pool() + } } impl RedisConnInterface for RouterStore { @@ -203,6 +211,14 @@ where fn get_replica_pool(&self) -> &PgPool { self.router_store.get_replica_pool() } + + fn get_accounts_master_pool(&self) -> &PgPool { + self.router_store.get_accounts_master_pool() + } + + fn get_accounts_replica_pool(&self) -> &PgPool { + self.router_store.get_accounts_replica_pool() + } } impl RedisConnInterface for KVRouterStore { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 4b7d194b6f6..ed2ea37307d 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -410,7 +410,8 @@ global_tenant = { tenant_id = "global", schema = "public", redis_key_prefix = "" [multitenancy.tenants.public] base_url = "http://localhost:8080" -schema = "public" +schema = "public" +accounts_schema = "public" redis_key_prefix = "" clickhouse_database = "default" diff --git a/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/down.sql b/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/down.sql new file mode 100644 index 00000000000..46c0d151e19 --- /dev/null +++ b/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/down.sql @@ -0,0 +1,3 @@ +-- This file should undo anything in `up.sql` +ALTER TABLE organization ALTER COLUMN org_id SET NOT NULL; +ALTER TABLE organization ADD PRIMARY KEY (org_id); \ No newline at end of file diff --git a/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/up.sql b/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/up.sql new file mode 100644 index 00000000000..886cb0bf583 --- /dev/null +++ b/v2_migrations/2024-08-28-081722_drop_not_null_constraints_on_v1_columns/up.sql @@ -0,0 +1,3 @@ +-- Drop not null constraint on org_id in orgnaization table +ALTER TABLE organization DROP CONSTRAINT organization_pkey; +ALTER TABLE organization ALTER COLUMN org_id DROP NOT NULL; \ No newline at end of file diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql index 661f7f294e0..5903aea89ff 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/down.sql @@ -6,9 +6,6 @@ WHERE org_id IS NULL; ALTER TABLE ORGANIZATION DROP CONSTRAINT organization_pkey_id; -ALTER TABLE ORGANIZATION -ADD CONSTRAINT organization_pkey PRIMARY KEY (org_id); - ALTER TABLE ORGANIZATION DROP CONSTRAINT organization_organization_name_key; -- back fill diff --git a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql index 3b1fe9b46af..0f5bd32e102 100644 --- a/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql +++ b/v2_migrations/2024-08-28-081838_update_v2_primary_key_constraints/up.sql @@ -11,8 +11,6 @@ WHERE organization_name IS NULL AND org_name IS NOT NULL; -- Alter queries for organization table -ALTER TABLE ORGANIZATION DROP CONSTRAINT organization_pkey; - ALTER TABLE ORGANIZATION ADD CONSTRAINT organization_pkey_id PRIMARY KEY (id); From c17eb01e35749343b3bf4fdda51782ea962ee57a Mon Sep 17 00:00:00 2001 From: Aniket Burman <93077964+aniketburman014@users.noreply.github.com> Date: Fri, 14 Feb 2025 18:21:57 +0530 Subject: [PATCH 102/133] feat(router): add v2 endpoint retrieve payment aggregate based on merchant profile (#7196) Co-authored-by: Aniket Burman Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../src/payments/payment_intent.rs | 2 +- crates/router/src/core/payments.rs | 2 +- crates/router/src/db/kafka_store.rs | 3 +- crates/router/src/routes/app.rs | 7 +++++ crates/router/src/routes/payments.rs | 31 ++++++++++++++++++- .../src/mock_db/payment_intent.rs | 2 +- .../src/payments/payment_intent.rs | 4 +-- 7 files changed, 43 insertions(+), 8 deletions(-) diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index e20b0860161..b2335550cf5 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -102,7 +102,7 @@ pub trait PaymentIntentInterface { storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; - #[cfg(all(feature = "v1", feature = "olap"))] + #[cfg(feature = "olap")] async fn get_intent_status_with_count( &self, merchant_id: &id_type::MerchantId, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 57e72a42941..e681c76afb5 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5408,7 +5408,7 @@ pub async fn get_payment_filters( )) } -#[cfg(all(feature = "olap", feature = "v1"))] +#[cfg(feature = "olap")] pub async fn get_aggregates_for_payments( state: SessionState, merchant: domain::MerchantAccount, diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index d4a1fee0929..d21c345b5fe 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1876,8 +1876,7 @@ impl PaymentIntentInterface for KafkaStore { ) .await } - - #[cfg(all(feature = "olap", feature = "v1"))] + #[cfg(feature = "olap")] async fn get_intent_status_with_count( &self, merchant_id: &id_type::MerchantId, diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index ee1e06af939..3da65e968b6 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -571,6 +571,13 @@ impl Payments { .service( web::resource("/create-intent") .route(web::post().to(payments::payments_create_intent)), + ) + .service( + web::resource("/aggregate").route(web::get().to(payments::get_payments_aggregates)), + ) + .service( + web::resource("/profile/aggregate") + .route(web::get().to(payments::get_payments_aggregates_profile)), ); route = diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index b55ab207f40..dd15079efed 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1407,7 +1407,7 @@ pub async fn get_payment_filters_profile( } #[instrument(skip_all, fields(flow = ?Flow::PaymentsAggregate))] -#[cfg(all(feature = "olap", feature = "v1"))] +#[cfg(feature = "olap")] pub async fn get_payments_aggregates( state: web::Data, req: actix_web::HttpRequest, @@ -2228,6 +2228,35 @@ pub async fn get_payments_aggregates_profile( )) .await } +#[instrument(skip_all, fields(flow = ?Flow::PaymentsAggregate))] +#[cfg(all(feature = "olap", feature = "v2"))] +pub async fn get_payments_aggregates_profile( + state: web::Data, + req: actix_web::HttpRequest, + payload: web::Query, +) -> impl Responder { + let flow = Flow::PaymentsAggregate; + let payload = payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, _| { + payments::get_aggregates_for_payments( + state, + auth.merchant_account, + Some(vec![auth.profile.get_id().clone()]), + req, + ) + }, + &auth::JWTAuth { + permission: Permission::ProfilePaymentRead, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} #[cfg(feature = "v2")] /// A private module to hold internal types to be used in route handlers. diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 0766d397bb7..6a46702e525 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -41,7 +41,7 @@ impl PaymentIntentInterface for MockDb { Err(StorageError::MockDbError)? } - #[cfg(all(feature = "v1", feature = "olap"))] + #[cfg(feature = "olap")] async fn get_intent_status_with_count( &self, _merchant_id: &common_utils::id_type::MerchantId, diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index a515898006b..b64d1aee364 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -412,7 +412,7 @@ impl PaymentIntentInterface for KVRouterStore { .await } - #[cfg(all(feature = "v1", feature = "olap"))] + #[cfg(feature = "olap")] async fn get_intent_status_with_count( &self, merchant_id: &common_utils::id_type::MerchantId, @@ -829,7 +829,7 @@ impl PaymentIntentInterface for crate::RouterStore { .await } - #[cfg(all(feature = "v1", feature = "olap"))] + #[cfg(feature = "olap")] #[instrument(skip_all)] async fn get_intent_status_with_count( &self, From 0d5c6faae06c9e6e793a271c121a43818fb3e53f Mon Sep 17 00:00:00 2001 From: Anurag Date: Fri, 14 Feb 2025 19:48:02 +0530 Subject: [PATCH 103/133] fix(cypress): Resolve cypress issue for NMI connector (#7267) Co-authored-by: Anurag Singh Co-authored-by: Anurag Singh --- cypress-tests/cypress/e2e/configs/Payment/Nmi.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js index d39b45172af..501aa4d3fde 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Nmi.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Nmi.js @@ -114,7 +114,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "requires_capture", }, }, }, @@ -308,7 +308,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "requires_capture", }, }, }, From c337be66f9ca8b3f0a2c0a510298d4f48f09f588 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Sat, 15 Feb 2025 20:39:26 +0530 Subject: [PATCH 104/133] feat(utils): add iso representation for each state for european countries (#7273) --- .typos.toml | 16 + crates/api_models/src/payments.rs | 2 +- crates/common_enums/src/enums.rs | 2703 +++++++++++++++++ crates/hyperswitch_connectors/src/utils.rs | 2067 ++++++++++++- .../hyperswitch_domain_models/src/mandates.rs | 2 +- crates/router/src/core/payments.rs | 5 +- 6 files changed, 4791 insertions(+), 4 deletions(-) diff --git a/.typos.toml b/.typos.toml index 983ead3f75d..75d1290eb46 100644 --- a/.typos.toml +++ b/.typos.toml @@ -43,6 +43,22 @@ ws2tcpip = "ws2tcpip" # WinSock Extension ZAR = "ZAR" # South African Rand currency code JOD = "JOD" # Jordan currency code Payed = "Payed" # Paid status for digital virgo +Alo = "Alo" # Is iso representation of a state in France +alo = "alo" # Is iso representation of a state in France +BRE = "BRE" # Is iso representation of Brittany +VasCounty = "VasCounty" # Is a state in Hungary +StipMunicipality = "StipMunicipality" # Is a municipality in North Macedonia +HAV = "HAV" # Is iso representation of a state in UK +LEW = "LEW" # Is iso representation of a state in UK +THR = "THR" # Is iso representation of a state in UK +WLL = "WLL" # Is iso representation of a state in UK +WIL = "WIL" # Is iso representation of a state in UK +YOR = "YOR" # Is iso representation of a state in UK +OT = "OT" # Is iso representation of a state in Romania +OltCounty = "OltCounty" # Is a state in Romania +olt = "olt" # Is iso representation of a state in Romania +Vas = "Vas" # Is iso representation of a state in Hungary +vas = "vas" # Is iso representation of a state in Hungary [default.extend-words] aci = "aci" # Name of a connector diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index fb033230654..f996de726e4 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1684,7 +1684,7 @@ impl MandateIds { pub struct MandateData { /// A way to update the mandate's payment method details pub update_mandate_id: Option, - /// A concent from the customer to store the payment method + /// A consent from the customer to store the payment method pub customer_acceptance: Option, /// A way to select the type of mandate used pub mandate_type: Option, diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index f7ea487e91e..b8efe58b4e2 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2421,6 +2421,2709 @@ pub enum CanadaStatesAbbreviation { YT, } +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum AlbaniaStatesAbbreviation { + #[strum(serialize = "01")] + Berat, + #[strum(serialize = "09")] + Diber, + #[strum(serialize = "02")] + Durres, + #[strum(serialize = "03")] + Elbasan, + #[strum(serialize = "04")] + Fier, + #[strum(serialize = "05")] + Gjirokaster, + #[strum(serialize = "06")] + Korce, + #[strum(serialize = "07")] + Kukes, + #[strum(serialize = "08")] + Lezhe, + #[strum(serialize = "10")] + Shkoder, + #[strum(serialize = "11")] + Tirane, + #[strum(serialize = "12")] + Vlore, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum AndorraStatesAbbreviation { + #[strum(serialize = "07")] + AndorraLaVella, + #[strum(serialize = "02")] + Canillo, + #[strum(serialize = "03")] + Encamp, + #[strum(serialize = "08")] + EscaldesEngordany, + #[strum(serialize = "04")] + LaMassana, + #[strum(serialize = "05")] + Ordino, + #[strum(serialize = "06")] + SantJuliaDeLoria, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum AustriaStatesAbbreviation { + #[strum(serialize = "1")] + Burgenland, + #[strum(serialize = "2")] + Carinthia, + #[strum(serialize = "3")] + LowerAustria, + #[strum(serialize = "5")] + Salzburg, + #[strum(serialize = "6")] + Styria, + #[strum(serialize = "7")] + Tyrol, + #[strum(serialize = "4")] + UpperAustria, + #[strum(serialize = "9")] + Vienna, + #[strum(serialize = "8")] + Vorarlberg, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum BelarusStatesAbbreviation { + #[strum(serialize = "BR")] + BrestRegion, + #[strum(serialize = "HO")] + GomelRegion, + #[strum(serialize = "HR")] + GrodnoRegion, + #[strum(serialize = "HM")] + Minsk, + #[strum(serialize = "MI")] + MinskRegion, + #[strum(serialize = "MA")] + MogilevRegion, + #[strum(serialize = "VI")] + VitebskRegion, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum BosniaAndHerzegovinaStatesAbbreviation { + #[strum(serialize = "05")] + BosnianPodrinjeCanton, + #[strum(serialize = "BRC")] + BrckoDistrict, + #[strum(serialize = "10")] + Canton10, + #[strum(serialize = "06")] + CentralBosniaCanton, + #[strum(serialize = "BIH")] + FederationOfBosniaAndHerzegovina, + #[strum(serialize = "07")] + HerzegovinaNeretvaCanton, + #[strum(serialize = "02")] + PosavinaCanton, + #[strum(serialize = "SRP")] + RepublikaSrpska, + #[strum(serialize = "09")] + SarajevoCanton, + #[strum(serialize = "03")] + TuzlaCanton, + #[strum(serialize = "01")] + UnaSanaCanton, + #[strum(serialize = "08")] + WestHerzegovinaCanton, + #[strum(serialize = "04")] + ZenicaDobojCanton, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum BulgariaStatesAbbreviation { + #[strum(serialize = "01")] + BlagoevgradProvince, + #[strum(serialize = "02")] + BurgasProvince, + #[strum(serialize = "08")] + DobrichProvince, + #[strum(serialize = "07")] + GabrovoProvince, + #[strum(serialize = "26")] + HaskovoProvince, + #[strum(serialize = "09")] + KardzhaliProvince, + #[strum(serialize = "10")] + KyustendilProvince, + #[strum(serialize = "11")] + LovechProvince, + #[strum(serialize = "12")] + MontanaProvince, + #[strum(serialize = "13")] + PazardzhikProvince, + #[strum(serialize = "14")] + PernikProvince, + #[strum(serialize = "15")] + PlevenProvince, + #[strum(serialize = "16")] + PlovdivProvince, + #[strum(serialize = "17")] + RazgradProvince, + #[strum(serialize = "18")] + RuseProvince, + #[strum(serialize = "27")] + Shumen, + #[strum(serialize = "19")] + SilistraProvince, + #[strum(serialize = "20")] + SlivenProvince, + #[strum(serialize = "21")] + SmolyanProvince, + #[strum(serialize = "22")] + SofiaCityProvince, + #[strum(serialize = "23")] + SofiaProvince, + #[strum(serialize = "24")] + StaraZagoraProvince, + #[strum(serialize = "25")] + TargovishteProvince, + #[strum(serialize = "03")] + VarnaProvince, + #[strum(serialize = "04")] + VelikoTarnovoProvince, + #[strum(serialize = "05")] + VidinProvince, + #[strum(serialize = "06")] + VratsaProvince, + #[strum(serialize = "28")] + YambolProvince, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum CroatiaStatesAbbreviation { + #[strum(serialize = "07")] + BjelovarBilogoraCounty, + #[strum(serialize = "12")] + BrodPosavinaCounty, + #[strum(serialize = "19")] + DubrovnikNeretvaCounty, + #[strum(serialize = "18")] + IstriaCounty, + #[strum(serialize = "06")] + KoprivnicaKrizevciCounty, + #[strum(serialize = "02")] + KrapinaZagorjeCounty, + #[strum(serialize = "09")] + LikaSenjCounty, + #[strum(serialize = "20")] + MedimurjeCounty, + #[strum(serialize = "14")] + OsijekBaranjaCounty, + #[strum(serialize = "11")] + PozegaSlavoniaCounty, + #[strum(serialize = "08")] + PrimorjeGorskiKotarCounty, + #[strum(serialize = "03")] + SisakMoslavinaCounty, + #[strum(serialize = "17")] + SplitDalmatiaCounty, + #[strum(serialize = "05")] + VarazdinCounty, + #[strum(serialize = "10")] + ViroviticaPodravinaCounty, + #[strum(serialize = "16")] + VukovarSyrmiaCounty, + #[strum(serialize = "13")] + ZadarCounty, + #[strum(serialize = "21")] + Zagreb, + #[strum(serialize = "01")] + ZagrebCounty, + #[strum(serialize = "15")] + SibenikKninCounty, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum CzechRepublicStatesAbbreviation { + #[strum(serialize = "201")] + BenesovDistrict, + #[strum(serialize = "202")] + BerounDistrict, + #[strum(serialize = "641")] + BlanskoDistrict, + #[strum(serialize = "642")] + BrnoCityDistrict, + #[strum(serialize = "643")] + BrnoCountryDistrict, + #[strum(serialize = "801")] + BruntalDistrict, + #[strum(serialize = "644")] + BreclavDistrict, + #[strum(serialize = "20")] + CentralBohemianRegion, + #[strum(serialize = "411")] + ChebDistrict, + #[strum(serialize = "422")] + ChomutovDistrict, + #[strum(serialize = "531")] + ChrudimDistrict, + #[strum(serialize = "321")] + DomazliceDistrict, + #[strum(serialize = "421")] + DecinDistrict, + #[strum(serialize = "802")] + FrydekMistekDistrict, + #[strum(serialize = "631")] + HavlickuvBrodDistrict, + #[strum(serialize = "645")] + HodoninDistrict, + #[strum(serialize = "120")] + HorniPocernice, + #[strum(serialize = "521")] + HradecKraloveDistrict, + #[strum(serialize = "52")] + HradecKraloveRegion, + #[strum(serialize = "512")] + JablonecNadNisouDistrict, + #[strum(serialize = "711")] + JesenikDistrict, + #[strum(serialize = "632")] + JihlavaDistrict, + #[strum(serialize = "313")] + JindrichuvHradecDistrict, + #[strum(serialize = "522")] + JicinDistrict, + #[strum(serialize = "412")] + KarlovyVaryDistrict, + #[strum(serialize = "41")] + KarlovyVaryRegion, + #[strum(serialize = "803")] + KarvinaDistrict, + #[strum(serialize = "203")] + KladnoDistrict, + #[strum(serialize = "322")] + KlatovyDistrict, + #[strum(serialize = "204")] + KolinDistrict, + #[strum(serialize = "721")] + KromerizDistrict, + #[strum(serialize = "513")] + LiberecDistrict, + #[strum(serialize = "51")] + LiberecRegion, + #[strum(serialize = "423")] + LitomericeDistrict, + #[strum(serialize = "424")] + LounyDistrict, + #[strum(serialize = "207")] + MladaBoleslavDistrict, + #[strum(serialize = "80")] + MoravianSilesianRegion, + #[strum(serialize = "425")] + MostDistrict, + #[strum(serialize = "206")] + MelnikDistrict, + #[strum(serialize = "804")] + NovyJicinDistrict, + #[strum(serialize = "208")] + NymburkDistrict, + #[strum(serialize = "523")] + NachodDistrict, + #[strum(serialize = "712")] + OlomoucDistrict, + #[strum(serialize = "71")] + OlomoucRegion, + #[strum(serialize = "805")] + OpavaDistrict, + #[strum(serialize = "806")] + OstravaCityDistrict, + #[strum(serialize = "532")] + PardubiceDistrict, + #[strum(serialize = "53")] + PardubiceRegion, + #[strum(serialize = "633")] + PelhrimovDistrict, + #[strum(serialize = "32")] + PlzenRegion, + #[strum(serialize = "323")] + PlzenCityDistrict, + #[strum(serialize = "325")] + PlzenNorthDistrict, + #[strum(serialize = "324")] + PlzenSouthDistrict, + #[strum(serialize = "315")] + PrachaticeDistrict, + #[strum(serialize = "10")] + Prague, + #[strum(serialize = "101")] + Prague1, + #[strum(serialize = "110")] + Prague10, + #[strum(serialize = "111")] + Prague11, + #[strum(serialize = "112")] + Prague12, + #[strum(serialize = "113")] + Prague13, + #[strum(serialize = "114")] + Prague14, + #[strum(serialize = "115")] + Prague15, + #[strum(serialize = "116")] + Prague16, + #[strum(serialize = "102")] + Prague2, + #[strum(serialize = "121")] + Prague21, + #[strum(serialize = "103")] + Prague3, + #[strum(serialize = "104")] + Prague4, + #[strum(serialize = "105")] + Prague5, + #[strum(serialize = "106")] + Prague6, + #[strum(serialize = "107")] + Prague7, + #[strum(serialize = "108")] + Prague8, + #[strum(serialize = "109")] + Prague9, + #[strum(serialize = "209")] + PragueEastDistrict, + #[strum(serialize = "20A")] + PragueWestDistrict, + #[strum(serialize = "713")] + ProstejovDistrict, + #[strum(serialize = "314")] + PisekDistrict, + #[strum(serialize = "714")] + PrerovDistrict, + #[strum(serialize = "20B")] + PribramDistrict, + #[strum(serialize = "20C")] + RakovnikDistrict, + #[strum(serialize = "326")] + RokycanyDistrict, + #[strum(serialize = "524")] + RychnovNadKneznouDistrict, + #[strum(serialize = "514")] + SemilyDistrict, + #[strum(serialize = "413")] + SokolovDistrict, + #[strum(serialize = "31")] + SouthBohemianRegion, + #[strum(serialize = "64")] + SouthMoravianRegion, + #[strum(serialize = "316")] + StrakoniceDistrict, + #[strum(serialize = "533")] + SvitavyDistrict, + #[strum(serialize = "327")] + TachovDistrict, + #[strum(serialize = "426")] + TepliceDistrict, + #[strum(serialize = "525")] + TrutnovDistrict, + #[strum(serialize = "317")] + TaborDistrict, + #[strum(serialize = "634")] + TrebicDistrict, + #[strum(serialize = "722")] + UherskeHradisteDistrict, + #[strum(serialize = "723")] + VsetinDistrict, + #[strum(serialize = "63")] + VysocinaRegion, + #[strum(serialize = "646")] + VyskovDistrict, + #[strum(serialize = "724")] + ZlinDistrict, + #[strum(serialize = "72")] + ZlinRegion, + #[strum(serialize = "647")] + ZnojmoDistrict, + #[strum(serialize = "427")] + UstiNadLabemDistrict, + #[strum(serialize = "42")] + UstiNadLabemRegion, + #[strum(serialize = "534")] + UstiNadOrliciDistrict, + #[strum(serialize = "511")] + CeskaLipaDistrict, + #[strum(serialize = "311")] + CeskeBudejoviceDistrict, + #[strum(serialize = "312")] + CeskyKrumlovDistrict, + #[strum(serialize = "715")] + SumperkDistrict, + #[strum(serialize = "635")] + ZdarNadSazavouDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum DenmarkStatesAbbreviation { + #[strum(serialize = "84")] + CapitalRegionOfDenmark, + #[strum(serialize = "82")] + CentralDenmarkRegion, + #[strum(serialize = "81")] + NorthDenmarkRegion, + #[strum(serialize = "85")] + RegionZealand, + #[strum(serialize = "83")] + RegionOfSouthernDenmark, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum FinlandStatesAbbreviation { + #[strum(serialize = "08")] + CentralFinland, + #[strum(serialize = "07")] + CentralOstrobothnia, + #[strum(serialize = "IS")] + EasternFinlandProvince, + #[strum(serialize = "19")] + FinlandProper, + #[strum(serialize = "05")] + Kainuu, + #[strum(serialize = "09")] + Kymenlaakso, + #[strum(serialize = "LL")] + Lapland, + #[strum(serialize = "13")] + NorthKarelia, + #[strum(serialize = "14")] + NorthernOstrobothnia, + #[strum(serialize = "15")] + NorthernSavonia, + #[strum(serialize = "12")] + Ostrobothnia, + #[strum(serialize = "OL")] + OuluProvince, + #[strum(serialize = "11")] + Pirkanmaa, + #[strum(serialize = "16")] + PaijanneTavastia, + #[strum(serialize = "17")] + Satakunta, + #[strum(serialize = "02")] + SouthKarelia, + #[strum(serialize = "03")] + SouthernOstrobothnia, + #[strum(serialize = "04")] + SouthernSavonia, + #[strum(serialize = "06")] + TavastiaProper, + #[strum(serialize = "18")] + Uusimaa, + #[strum(serialize = "01")] + AlandIslands, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum FranceStatesAbbreviation { + #[strum(serialize = "WF-AL")] + Alo, + #[strum(serialize = "A")] + Alsace, + #[strum(serialize = "B")] + Aquitaine, + #[strum(serialize = "C")] + Auvergne, + #[strum(serialize = "ARA")] + AuvergneRhoneAlpes, + #[strum(serialize = "BFC")] + BourgogneFrancheComte, + #[strum(serialize = "BRE")] + Brittany, + #[strum(serialize = "D")] + Burgundy, + #[strum(serialize = "CVL")] + CentreValDeLoire, + #[strum(serialize = "G")] + ChampagneArdenne, + #[strum(serialize = "COR")] + Corsica, + #[strum(serialize = "I")] + FrancheComte, + #[strum(serialize = "GF")] + FrenchGuiana, + #[strum(serialize = "PF")] + FrenchPolynesia, + #[strum(serialize = "GES")] + GrandEst, + #[strum(serialize = "GP")] + Guadeloupe, + #[strum(serialize = "HDF")] + HautsDeFrance, + #[strum(serialize = "K")] + LanguedocRoussillon, + #[strum(serialize = "L")] + Limousin, + #[strum(serialize = "M")] + Lorraine, + #[strum(serialize = "P")] + LowerNormandy, + #[strum(serialize = "MQ")] + Martinique, + #[strum(serialize = "YT")] + Mayotte, + #[strum(serialize = "O")] + NordPasDeCalais, + #[strum(serialize = "NOR")] + Normandy, + #[strum(serialize = "NAQ")] + NouvelleAquitaine, + #[strum(serialize = "OCC")] + Occitania, + #[strum(serialize = "75")] + Paris, + #[strum(serialize = "PDL")] + PaysDeLaLoire, + #[strum(serialize = "S")] + Picardy, + #[strum(serialize = "T")] + PoitouCharentes, + #[strum(serialize = "PAC")] + ProvenceAlpesCoteDAzur, + #[strum(serialize = "V")] + RhoneAlpes, + #[strum(serialize = "RE")] + Reunion, + #[strum(serialize = "BL")] + SaintBarthelemy, + #[strum(serialize = "MF")] + SaintMartin, + #[strum(serialize = "PM")] + SaintPierreAndMiquelon, + #[strum(serialize = "WF-SG")] + Sigave, + #[strum(serialize = "Q")] + UpperNormandy, + #[strum(serialize = "WF-UV")] + Uvea, + #[strum(serialize = "WF")] + WallisAndFutuna, + #[strum(serialize = "IDF")] + IleDeFrance, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum GermanyStatesAbbreviation { + #[strum(serialize = "BW")] + BadenWurttemberg, + #[strum(serialize = "BY")] + Bavaria, + #[strum(serialize = "BE")] + Berlin, + #[strum(serialize = "BB")] + Brandenburg, + #[strum(serialize = "HB")] + Bremen, + #[strum(serialize = "HH")] + Hamburg, + #[strum(serialize = "HE")] + Hesse, + #[strum(serialize = "NI")] + LowerSaxony, + #[strum(serialize = "MV")] + MecklenburgVorpommern, + #[strum(serialize = "NW")] + NorthRhineWestphalia, + #[strum(serialize = "RP")] + RhinelandPalatinate, + #[strum(serialize = "SL")] + Saarland, + #[strum(serialize = "SN")] + Saxony, + #[strum(serialize = "ST")] + SaxonyAnhalt, + #[strum(serialize = "SH")] + SchleswigHolstein, + #[strum(serialize = "TH")] + Thuringia, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum GreeceStatesAbbreviation { + #[strum(serialize = "13")] + AchaeaRegionalUnit, + #[strum(serialize = "01")] + AetoliaAcarnaniaRegionalUnit, + #[strum(serialize = "12")] + ArcadiaPrefecture, + #[strum(serialize = "11")] + ArgolisRegionalUnit, + #[strum(serialize = "I")] + AtticaRegion, + #[strum(serialize = "03")] + BoeotiaRegionalUnit, + #[strum(serialize = "H")] + CentralGreeceRegion, + #[strum(serialize = "B")] + CentralMacedonia, + #[strum(serialize = "94")] + ChaniaRegionalUnit, + #[strum(serialize = "22")] + CorfuPrefecture, + #[strum(serialize = "15")] + CorinthiaRegionalUnit, + #[strum(serialize = "M")] + CreteRegion, + #[strum(serialize = "52")] + DramaRegionalUnit, + #[strum(serialize = "A2")] + EastAtticaRegionalUnit, + #[strum(serialize = "A")] + EastMacedoniaAndThrace, + #[strum(serialize = "D")] + EpirusRegion, + #[strum(serialize = "04")] + Euboea, + #[strum(serialize = "51")] + GrevenaPrefecture, + #[strum(serialize = "53")] + ImathiaRegionalUnit, + #[strum(serialize = "33")] + IoanninaRegionalUnit, + #[strum(serialize = "F")] + IonianIslandsRegion, + #[strum(serialize = "41")] + KarditsaRegionalUnit, + #[strum(serialize = "56")] + KastoriaRegionalUnit, + #[strum(serialize = "23")] + KefaloniaPrefecture, + #[strum(serialize = "57")] + KilkisRegionalUnit, + #[strum(serialize = "58")] + KozaniPrefecture, + #[strum(serialize = "16")] + Laconia, + #[strum(serialize = "42")] + LarissaPrefecture, + #[strum(serialize = "24")] + LefkadaRegionalUnit, + #[strum(serialize = "59")] + PellaRegionalUnit, + #[strum(serialize = "J")] + PeloponneseRegion, + #[strum(serialize = "06")] + PhthiotisPrefecture, + #[strum(serialize = "34")] + PrevezaPrefecture, + #[strum(serialize = "62")] + SerresPrefecture, + #[strum(serialize = "L")] + SouthAegean, + #[strum(serialize = "54")] + ThessalonikiRegionalUnit, + #[strum(serialize = "G")] + WestGreeceRegion, + #[strum(serialize = "C")] + WestMacedoniaRegion, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum HungaryStatesAbbreviation { + #[strum(serialize = "BA")] + BaranyaCounty, + #[strum(serialize = "BZ")] + BorsodAbaujZemplenCounty, + #[strum(serialize = "BU")] + Budapest, + #[strum(serialize = "BK")] + BacsKiskunCounty, + #[strum(serialize = "BE")] + BekesCounty, + #[strum(serialize = "BC")] + Bekescsaba, + #[strum(serialize = "CS")] + CsongradCounty, + #[strum(serialize = "DE")] + Debrecen, + #[strum(serialize = "DU")] + Dunaujvaros, + #[strum(serialize = "EG")] + Eger, + #[strum(serialize = "FE")] + FejerCounty, + #[strum(serialize = "GY")] + Gyor, + #[strum(serialize = "GS")] + GyorMosonSopronCounty, + #[strum(serialize = "HB")] + HajduBiharCounty, + #[strum(serialize = "HE")] + HevesCounty, + #[strum(serialize = "HV")] + Hodmezovasarhely, + #[strum(serialize = "JN")] + JaszNagykunSzolnokCounty, + #[strum(serialize = "KV")] + Kaposvar, + #[strum(serialize = "KM")] + Kecskemet, + #[strum(serialize = "MI")] + Miskolc, + #[strum(serialize = "NK")] + Nagykanizsa, + #[strum(serialize = "NY")] + Nyiregyhaza, + #[strum(serialize = "NO")] + NogradCounty, + #[strum(serialize = "PE")] + PestCounty, + #[strum(serialize = "PS")] + Pecs, + #[strum(serialize = "ST")] + Salgotarjan, + #[strum(serialize = "SO")] + SomogyCounty, + #[strum(serialize = "SN")] + Sopron, + #[strum(serialize = "SZ")] + SzabolcsSzatmarBeregCounty, + #[strum(serialize = "SD")] + Szeged, + #[strum(serialize = "SS")] + Szekszard, + #[strum(serialize = "SK")] + Szolnok, + #[strum(serialize = "SH")] + Szombathely, + #[strum(serialize = "SF")] + Szekesfehervar, + #[strum(serialize = "TB")] + Tatabanya, + #[strum(serialize = "TO")] + TolnaCounty, + #[strum(serialize = "VA")] + VasCounty, + #[strum(serialize = "VM")] + Veszprem, + #[strum(serialize = "VE")] + VeszpremCounty, + #[strum(serialize = "ZA")] + ZalaCounty, + #[strum(serialize = "ZE")] + Zalaegerszeg, + #[strum(serialize = "ER")] + Erd, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum IcelandStatesAbbreviation { + #[strum(serialize = "1")] + CapitalRegion, + #[strum(serialize = "7")] + EasternRegion, + #[strum(serialize = "6")] + NortheasternRegion, + #[strum(serialize = "5")] + NorthwesternRegion, + #[strum(serialize = "2")] + SouthernPeninsulaRegion, + #[strum(serialize = "8")] + SouthernRegion, + #[strum(serialize = "3")] + WesternRegion, + #[strum(serialize = "4")] + Westfjords, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum IrelandStatesAbbreviation { + #[strum(serialize = "C")] + Connacht, + #[strum(serialize = "CW")] + CountyCarlow, + #[strum(serialize = "CN")] + CountyCavan, + #[strum(serialize = "CE")] + CountyClare, + #[strum(serialize = "CO")] + CountyCork, + #[strum(serialize = "DL")] + CountyDonegal, + #[strum(serialize = "D")] + CountyDublin, + #[strum(serialize = "G")] + CountyGalway, + #[strum(serialize = "KY")] + CountyKerry, + #[strum(serialize = "KE")] + CountyKildare, + #[strum(serialize = "KK")] + CountyKilkenny, + #[strum(serialize = "LS")] + CountyLaois, + #[strum(serialize = "LK")] + CountyLimerick, + #[strum(serialize = "LD")] + CountyLongford, + #[strum(serialize = "LH")] + CountyLouth, + #[strum(serialize = "MO")] + CountyMayo, + #[strum(serialize = "MH")] + CountyMeath, + #[strum(serialize = "MN")] + CountyMonaghan, + #[strum(serialize = "OY")] + CountyOffaly, + #[strum(serialize = "RN")] + CountyRoscommon, + #[strum(serialize = "SO")] + CountySligo, + #[strum(serialize = "TA")] + CountyTipperary, + #[strum(serialize = "WD")] + CountyWaterford, + #[strum(serialize = "WH")] + CountyWestmeath, + #[strum(serialize = "WX")] + CountyWexford, + #[strum(serialize = "WW")] + CountyWicklow, + #[strum(serialize = "L")] + Leinster, + #[strum(serialize = "M")] + Munster, + #[strum(serialize = "U")] + Ulster, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum LatviaStatesAbbreviation { + #[strum(serialize = "001")] + AglonaMunicipality, + #[strum(serialize = "002")] + AizkraukleMunicipality, + #[strum(serialize = "003")] + AizputeMunicipality, + #[strum(serialize = "004")] + AknīsteMunicipality, + #[strum(serialize = "005")] + AlojaMunicipality, + #[strum(serialize = "006")] + AlsungaMunicipality, + #[strum(serialize = "007")] + AlūksneMunicipality, + #[strum(serialize = "008")] + AmataMunicipality, + #[strum(serialize = "009")] + ApeMunicipality, + #[strum(serialize = "010")] + AuceMunicipality, + #[strum(serialize = "012")] + BabīteMunicipality, + #[strum(serialize = "013")] + BaldoneMunicipality, + #[strum(serialize = "014")] + BaltinavaMunicipality, + #[strum(serialize = "015")] + BalviMunicipality, + #[strum(serialize = "016")] + BauskaMunicipality, + #[strum(serialize = "017")] + BeverīnaMunicipality, + #[strum(serialize = "018")] + BrocēniMunicipality, + #[strum(serialize = "019")] + BurtniekiMunicipality, + #[strum(serialize = "020")] + CarnikavaMunicipality, + #[strum(serialize = "021")] + CesvaineMunicipality, + #[strum(serialize = "023")] + CiblaMunicipality, + #[strum(serialize = "022")] + CēsisMunicipality, + #[strum(serialize = "024")] + DagdaMunicipality, + #[strum(serialize = "DGV")] + Daugavpils, + #[strum(serialize = "025")] + DaugavpilsMunicipality, + #[strum(serialize = "026")] + DobeleMunicipality, + #[strum(serialize = "027")] + DundagaMunicipality, + #[strum(serialize = "028")] + DurbeMunicipality, + #[strum(serialize = "029")] + EngureMunicipality, + #[strum(serialize = "031")] + GarkalneMunicipality, + #[strum(serialize = "032")] + GrobiņaMunicipality, + #[strum(serialize = "033")] + GulbeneMunicipality, + #[strum(serialize = "034")] + IecavaMunicipality, + #[strum(serialize = "035")] + IkšķileMunicipality, + #[strum(serialize = "036")] + IlūksteMunicipality, + #[strum(serialize = "037")] + InčukalnsMunicipality, + #[strum(serialize = "038")] + JaunjelgavaMunicipality, + #[strum(serialize = "039")] + JaunpiebalgaMunicipality, + #[strum(serialize = "040")] + JaunpilsMunicipality, + #[strum(serialize = "JEL")] + Jelgava, + #[strum(serialize = "041")] + JelgavaMunicipality, + #[strum(serialize = "JKB")] + Jēkabpils, + #[strum(serialize = "042")] + JēkabpilsMunicipality, + #[strum(serialize = "JUR")] + Jūrmala, + #[strum(serialize = "043")] + KandavaMunicipality, + #[strum(serialize = "045")] + KocēniMunicipality, + #[strum(serialize = "046")] + KokneseMunicipality, + #[strum(serialize = "048")] + KrimuldaMunicipality, + #[strum(serialize = "049")] + KrustpilsMunicipality, + #[strum(serialize = "047")] + KrāslavaMunicipality, + #[strum(serialize = "050")] + KuldīgaMunicipality, + #[strum(serialize = "044")] + KārsavaMunicipality, + #[strum(serialize = "053")] + LielvārdeMunicipality, + #[strum(serialize = "LPX")] + Liepāja, + #[strum(serialize = "054")] + LimbažiMunicipality, + #[strum(serialize = "057")] + LubānaMunicipality, + #[strum(serialize = "058")] + LudzaMunicipality, + #[strum(serialize = "055")] + LīgatneMunicipality, + #[strum(serialize = "056")] + LīvāniMunicipality, + #[strum(serialize = "059")] + MadonaMunicipality, + #[strum(serialize = "060")] + MazsalacaMunicipality, + #[strum(serialize = "061")] + MālpilsMunicipality, + #[strum(serialize = "062")] + MārupeMunicipality, + #[strum(serialize = "063")] + MērsragsMunicipality, + #[strum(serialize = "064")] + NaukšēniMunicipality, + #[strum(serialize = "065")] + NeretaMunicipality, + #[strum(serialize = "066")] + NīcaMunicipality, + #[strum(serialize = "067")] + OgreMunicipality, + #[strum(serialize = "068")] + OlaineMunicipality, + #[strum(serialize = "069")] + OzolniekiMunicipality, + #[strum(serialize = "073")] + PreiļiMunicipality, + #[strum(serialize = "074")] + PriekuleMunicipality, + #[strum(serialize = "075")] + PriekuļiMunicipality, + #[strum(serialize = "070")] + PārgaujaMunicipality, + #[strum(serialize = "071")] + PāvilostaMunicipality, + #[strum(serialize = "072")] + PļaviņasMunicipality, + #[strum(serialize = "076")] + RaunaMunicipality, + #[strum(serialize = "078")] + RiebiņiMunicipality, + #[strum(serialize = "RIX")] + Riga, + #[strum(serialize = "079")] + RojaMunicipality, + #[strum(serialize = "080")] + RopažiMunicipality, + #[strum(serialize = "081")] + RucavaMunicipality, + #[strum(serialize = "082")] + RugājiMunicipality, + #[strum(serialize = "083")] + RundāleMunicipality, + #[strum(serialize = "REZ")] + Rēzekne, + #[strum(serialize = "077")] + RēzekneMunicipality, + #[strum(serialize = "084")] + RūjienaMunicipality, + #[strum(serialize = "085")] + SalaMunicipality, + #[strum(serialize = "086")] + SalacgrīvaMunicipality, + #[strum(serialize = "087")] + SalaspilsMunicipality, + #[strum(serialize = "088")] + SaldusMunicipality, + #[strum(serialize = "089")] + SaulkrastiMunicipality, + #[strum(serialize = "091")] + SiguldaMunicipality, + #[strum(serialize = "093")] + SkrundaMunicipality, + #[strum(serialize = "092")] + SkrīveriMunicipality, + #[strum(serialize = "094")] + SmilteneMunicipality, + #[strum(serialize = "095")] + StopiņiMunicipality, + #[strum(serialize = "096")] + StrenčiMunicipality, + #[strum(serialize = "090")] + SējaMunicipality, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum ItalyStatesAbbreviation { + #[strum(serialize = "65")] + Abruzzo, + #[strum(serialize = "23")] + AostaValley, + #[strum(serialize = "75")] + Apulia, + #[strum(serialize = "77")] + Basilicata, + #[strum(serialize = "BN")] + BeneventoProvince, + #[strum(serialize = "78")] + Calabria, + #[strum(serialize = "72")] + Campania, + #[strum(serialize = "45")] + EmiliaRomagna, + #[strum(serialize = "36")] + FriuliVeneziaGiulia, + #[strum(serialize = "62")] + Lazio, + #[strum(serialize = "42")] + Liguria, + #[strum(serialize = "25")] + Lombardy, + #[strum(serialize = "57")] + Marche, + #[strum(serialize = "67")] + Molise, + #[strum(serialize = "21")] + Piedmont, + #[strum(serialize = "88")] + Sardinia, + #[strum(serialize = "82")] + Sicily, + #[strum(serialize = "32")] + TrentinoSouthTyrol, + #[strum(serialize = "52")] + Tuscany, + #[strum(serialize = "55")] + Umbria, + #[strum(serialize = "34")] + Veneto, + #[strum(serialize = "AG")] + Agrigento, + #[strum(serialize = "CL")] + Caltanissetta, + #[strum(serialize = "EN")] + Enna, + #[strum(serialize = "RG")] + Ragusa, + #[strum(serialize = "SR")] + Siracusa, + #[strum(serialize = "TP")] + Trapani, + #[strum(serialize = "BA")] + Bari, + #[strum(serialize = "BO")] + Bologna, + #[strum(serialize = "CA")] + Cagliari, + #[strum(serialize = "CT")] + Catania, + #[strum(serialize = "FI")] + Florence, + #[strum(serialize = "GE")] + Genoa, + #[strum(serialize = "ME")] + Messina, + #[strum(serialize = "MI")] + Milan, + #[strum(serialize = "NA")] + Naples, + #[strum(serialize = "PA")] + Palermo, + #[strum(serialize = "RC")] + ReggioCalabria, + #[strum(serialize = "RM")] + Rome, + #[strum(serialize = "TO")] + Turin, + #[strum(serialize = "VE")] + Venice, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum LiechtensteinStatesAbbreviation { + #[strum(serialize = "01")] + Balzers, + #[strum(serialize = "02")] + Eschen, + #[strum(serialize = "03")] + Gamprin, + #[strum(serialize = "04")] + Mauren, + #[strum(serialize = "05")] + Planken, + #[strum(serialize = "06")] + Ruggell, + #[strum(serialize = "07")] + Schaan, + #[strum(serialize = "08")] + Schellenberg, + #[strum(serialize = "09")] + Triesen, + #[strum(serialize = "10")] + Triesenberg, + #[strum(serialize = "11")] + Vaduz, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum LithuaniaStatesAbbreviation { + #[strum(serialize = "01")] + AkmeneDistrictMunicipality, + #[strum(serialize = "02")] + AlytusCityMunicipality, + #[strum(serialize = "AL")] + AlytusCounty, + #[strum(serialize = "03")] + AlytusDistrictMunicipality, + #[strum(serialize = "05")] + BirstonasMunicipality, + #[strum(serialize = "06")] + BirzaiDistrictMunicipality, + #[strum(serialize = "07")] + DruskininkaiMunicipality, + #[strum(serialize = "08")] + ElektrenaiMunicipality, + #[strum(serialize = "09")] + IgnalinaDistrictMunicipality, + #[strum(serialize = "10")] + JonavaDistrictMunicipality, + #[strum(serialize = "11")] + JoniskisDistrictMunicipality, + #[strum(serialize = "12")] + JurbarkasDistrictMunicipality, + #[strum(serialize = "13")] + KaisiadorysDistrictMunicipality, + #[strum(serialize = "14")] + KalvarijaMunicipality, + #[strum(serialize = "15")] + KaunasCityMunicipality, + #[strum(serialize = "KU")] + KaunasCounty, + #[strum(serialize = "16")] + KaunasDistrictMunicipality, + #[strum(serialize = "17")] + KazluRudaMunicipality, + #[strum(serialize = "19")] + KelmeDistrictMunicipality, + #[strum(serialize = "20")] + KlaipedaCityMunicipality, + #[strum(serialize = "KL")] + KlaipedaCounty, + #[strum(serialize = "21")] + KlaipedaDistrictMunicipality, + #[strum(serialize = "22")] + KretingaDistrictMunicipality, + #[strum(serialize = "23")] + KupiskisDistrictMunicipality, + #[strum(serialize = "18")] + KedainiaiDistrictMunicipality, + #[strum(serialize = "24")] + LazdijaiDistrictMunicipality, + #[strum(serialize = "MR")] + MarijampoleCounty, + #[strum(serialize = "25")] + MarijampoleMunicipality, + #[strum(serialize = "26")] + MazeikiaiDistrictMunicipality, + #[strum(serialize = "27")] + MoletaiDistrictMunicipality, + #[strum(serialize = "28")] + NeringaMunicipality, + #[strum(serialize = "29")] + PagegiaiMunicipality, + #[strum(serialize = "30")] + PakruojisDistrictMunicipality, + #[strum(serialize = "31")] + PalangaCityMunicipality, + #[strum(serialize = "32")] + PanevezysCityMunicipality, + #[strum(serialize = "PN")] + PanevezysCounty, + #[strum(serialize = "33")] + PanevezysDistrictMunicipality, + #[strum(serialize = "34")] + PasvalysDistrictMunicipality, + #[strum(serialize = "35")] + PlungeDistrictMunicipality, + #[strum(serialize = "36")] + PrienaiDistrictMunicipality, + #[strum(serialize = "37")] + RadviliskisDistrictMunicipality, + #[strum(serialize = "38")] + RaseiniaiDistrictMunicipality, + #[strum(serialize = "39")] + RietavasMunicipality, + #[strum(serialize = "40")] + RokiskisDistrictMunicipality, + #[strum(serialize = "48")] + SkuodasDistrictMunicipality, + #[strum(serialize = "TA")] + TaurageCounty, + #[strum(serialize = "50")] + TaurageDistrictMunicipality, + #[strum(serialize = "TE")] + TelsiaiCounty, + #[strum(serialize = "51")] + TelsiaiDistrictMunicipality, + #[strum(serialize = "52")] + TrakaiDistrictMunicipality, + #[strum(serialize = "53")] + UkmergeDistrictMunicipality, + #[strum(serialize = "UT")] + UtenaCounty, + #[strum(serialize = "54")] + UtenaDistrictMunicipality, + #[strum(serialize = "55")] + VarenaDistrictMunicipality, + #[strum(serialize = "56")] + VilkaviskisDistrictMunicipality, + #[strum(serialize = "57")] + VilniusCityMunicipality, + #[strum(serialize = "VL")] + VilniusCounty, + #[strum(serialize = "58")] + VilniusDistrictMunicipality, + #[strum(serialize = "59")] + VisaginasMunicipality, + #[strum(serialize = "60")] + ZarasaiDistrictMunicipality, + #[strum(serialize = "41")] + SakiaiDistrictMunicipality, + #[strum(serialize = "42")] + SalcininkaiDistrictMunicipality, + #[strum(serialize = "43")] + SiauliaiCityMunicipality, + #[strum(serialize = "SA")] + SiauliaiCounty, + #[strum(serialize = "44")] + SiauliaiDistrictMunicipality, + #[strum(serialize = "45")] + SilaleDistrictMunicipality, + #[strum(serialize = "46")] + SiluteDistrictMunicipality, + #[strum(serialize = "47")] + SirvintosDistrictMunicipality, + #[strum(serialize = "49")] + SvencionysDistrictMunicipality, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum MaltaStatesAbbreviation { + #[strum(serialize = "01")] + Attard, + #[strum(serialize = "02")] + Balzan, + #[strum(serialize = "03")] + Birgu, + #[strum(serialize = "04")] + Birkirkara, + #[strum(serialize = "05")] + Birzebbuga, + #[strum(serialize = "06")] + Cospicua, + #[strum(serialize = "07")] + Dingli, + #[strum(serialize = "08")] + Fgura, + #[strum(serialize = "09")] + Floriana, + #[strum(serialize = "10")] + Fontana, + #[strum(serialize = "11")] + Gudja, + #[strum(serialize = "12")] + Gzira, + #[strum(serialize = "13")] + Ghajnsielem, + #[strum(serialize = "14")] + Gharb, + #[strum(serialize = "15")] + Gharghur, + #[strum(serialize = "16")] + Ghasri, + #[strum(serialize = "17")] + Ghaxaq, + #[strum(serialize = "18")] + Hamrun, + #[strum(serialize = "19")] + Iklin, + #[strum(serialize = "20")] + Senglea, + #[strum(serialize = "21")] + Kalkara, + #[strum(serialize = "22")] + Kercem, + #[strum(serialize = "23")] + Kirkop, + #[strum(serialize = "24")] + Lija, + #[strum(serialize = "25")] + Luqa, + #[strum(serialize = "26")] + Marsa, + #[strum(serialize = "27")] + Marsaskala, + #[strum(serialize = "28")] + Marsaxlokk, + #[strum(serialize = "29")] + Mdina, + #[strum(serialize = "30")] + Mellieha, + #[strum(serialize = "31")] + Mgarr, + #[strum(serialize = "32")] + Mosta, + #[strum(serialize = "33")] + Mqabba, + #[strum(serialize = "34")] + Msida, + #[strum(serialize = "35")] + Mtarfa, + #[strum(serialize = "36")] + Munxar, + #[strum(serialize = "37")] + Nadur, + #[strum(serialize = "38")] + Naxxar, + #[strum(serialize = "39")] + Paola, + #[strum(serialize = "40")] + Pembroke, + #[strum(serialize = "41")] + Pieta, + #[strum(serialize = "42")] + Qala, + #[strum(serialize = "43")] + Qormi, + #[strum(serialize = "44")] + Qrendi, + #[strum(serialize = "45")] + Victoria, + #[strum(serialize = "46")] + Rabat, + #[strum(serialize = "48")] + StJulians, + #[strum(serialize = "49")] + SanGwann, + #[strum(serialize = "50")] + SaintLawrence, + #[strum(serialize = "51")] + StPaulsBay, + #[strum(serialize = "52")] + Sannat, + #[strum(serialize = "53")] + SantaLucija, + #[strum(serialize = "54")] + SantaVenera, + #[strum(serialize = "55")] + Siggiewi, + #[strum(serialize = "56")] + Sliema, + #[strum(serialize = "57")] + Swieqi, + #[strum(serialize = "58")] + TaXbiex, + #[strum(serialize = "59")] + Tarxien, + #[strum(serialize = "60")] + Valletta, + #[strum(serialize = "61")] + Xaghra, + #[strum(serialize = "62")] + Xewkija, + #[strum(serialize = "63")] + Xghajra, + #[strum(serialize = "64")] + Zabbar, + #[strum(serialize = "65")] + ZebbugGozo, + #[strum(serialize = "66")] + ZebbugMalta, + #[strum(serialize = "67")] + Zejtun, + #[strum(serialize = "68")] + Zurrieq, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum MoldovaStatesAbbreviation { + #[strum(serialize = "AN")] + AneniiNoiDistrict, + #[strum(serialize = "BS")] + BasarabeascaDistrict, + #[strum(serialize = "BD")] + BenderMunicipality, + #[strum(serialize = "BR")] + BriceniDistrict, + #[strum(serialize = "BA")] + BaltiMunicipality, + #[strum(serialize = "CA")] + CahulDistrict, + #[strum(serialize = "CT")] + CantemirDistrict, + #[strum(serialize = "CU")] + ChisinauMunicipality, + #[strum(serialize = "CM")] + CimisliaDistrict, + #[strum(serialize = "CR")] + CriuleniDistrict, + #[strum(serialize = "CL")] + CalarasiDistrict, + #[strum(serialize = "CS")] + CauseniDistrict, + #[strum(serialize = "DO")] + DonduseniDistrict, + #[strum(serialize = "DR")] + DrochiaDistrict, + #[strum(serialize = "DU")] + DubasariDistrict, + #[strum(serialize = "ED")] + EdinetDistrict, + #[strum(serialize = "FL")] + FlorestiDistrict, + #[strum(serialize = "FA")] + FalestiDistrict, + #[strum(serialize = "GA")] + Gagauzia, + #[strum(serialize = "GL")] + GlodeniDistrict, + #[strum(serialize = "HI")] + HincestiDistrict, + #[strum(serialize = "IA")] + IaloveniDistrict, + #[strum(serialize = "NI")] + NisporeniDistrict, + #[strum(serialize = "OC")] + OcnitaDistrict, + #[strum(serialize = "OR")] + OrheiDistrict, + #[strum(serialize = "RE")] + RezinaDistrict, + #[strum(serialize = "RI")] + RiscaniDistrict, + #[strum(serialize = "SO")] + SorocaDistrict, + #[strum(serialize = "ST")] + StraseniDistrict, + #[strum(serialize = "SI")] + SingereiDistrict, + #[strum(serialize = "TA")] + TaracliaDistrict, + #[strum(serialize = "TE")] + TelenestiDistrict, + #[strum(serialize = "SN")] + TransnistriaAutonomousTerritorialUnit, + #[strum(serialize = "UN")] + UngheniDistrict, + #[strum(serialize = "SD")] + SoldanestiDistrict, + #[strum(serialize = "SV")] + StefanVodaDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum MonacoStatesAbbreviation { + Monaco, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum MontenegroStatesAbbreviation { + #[strum(serialize = "01")] + AndrijevicaMunicipality, + #[strum(serialize = "02")] + BarMunicipality, + #[strum(serialize = "03")] + BeraneMunicipality, + #[strum(serialize = "04")] + BijeloPoljeMunicipality, + #[strum(serialize = "05")] + BudvaMunicipality, + #[strum(serialize = "07")] + DanilovgradMunicipality, + #[strum(serialize = "22")] + GusinjeMunicipality, + #[strum(serialize = "09")] + KolasinMunicipality, + #[strum(serialize = "10")] + KotorMunicipality, + #[strum(serialize = "11")] + MojkovacMunicipality, + #[strum(serialize = "12")] + NiksicMunicipality, + #[strum(serialize = "06")] + OldRoyalCapitalCetinje, + #[strum(serialize = "23")] + PetnjicaMunicipality, + #[strum(serialize = "13")] + PlavMunicipality, + #[strum(serialize = "14")] + PljevljaMunicipality, + #[strum(serialize = "15")] + PluzineMunicipality, + #[strum(serialize = "16")] + PodgoricaMunicipality, + #[strum(serialize = "17")] + RozajeMunicipality, + #[strum(serialize = "19")] + TivatMunicipality, + #[strum(serialize = "20")] + UlcinjMunicipality, + #[strum(serialize = "18")] + SavnikMunicipality, + #[strum(serialize = "21")] + ZabljakMunicipality, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum NetherlandsStatesAbbreviation { + #[strum(serialize = "BQ1")] + Bonaire, + #[strum(serialize = "DR")] + Drenthe, + #[strum(serialize = "FL")] + Flevoland, + #[strum(serialize = "FR")] + Friesland, + #[strum(serialize = "GE")] + Gelderland, + #[strum(serialize = "GR")] + Groningen, + #[strum(serialize = "LI")] + Limburg, + #[strum(serialize = "NB")] + NorthBrabant, + #[strum(serialize = "NH")] + NorthHolland, + #[strum(serialize = "OV")] + Overijssel, + #[strum(serialize = "BQ2")] + Saba, + #[strum(serialize = "BQ3")] + SintEustatius, + #[strum(serialize = "ZH")] + SouthHolland, + #[strum(serialize = "UT")] + Utrecht, + #[strum(serialize = "ZE")] + Zeeland, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum NorthMacedoniaStatesAbbreviation { + #[strum(serialize = "01")] + AerodromMunicipality, + #[strum(serialize = "02")] + AracinovoMunicipality, + #[strum(serialize = "03")] + BerovoMunicipality, + #[strum(serialize = "04")] + BitolaMunicipality, + #[strum(serialize = "05")] + BogdanciMunicipality, + #[strum(serialize = "06")] + BogovinjeMunicipality, + #[strum(serialize = "07")] + BosilovoMunicipality, + #[strum(serialize = "08")] + BrvenicaMunicipality, + #[strum(serialize = "09")] + ButelMunicipality, + #[strum(serialize = "77")] + CentarMunicipality, + #[strum(serialize = "78")] + CentarZupaMunicipality, + #[strum(serialize = "22")] + DebarcaMunicipality, + #[strum(serialize = "23")] + DelcevoMunicipality, + #[strum(serialize = "25")] + DemirHisarMunicipality, + #[strum(serialize = "24")] + DemirKapijaMunicipality, + #[strum(serialize = "26")] + DojranMunicipality, + #[strum(serialize = "27")] + DolneniMunicipality, + #[strum(serialize = "28")] + DrugovoMunicipality, + #[strum(serialize = "17")] + GaziBabaMunicipality, + #[strum(serialize = "18")] + GevgelijaMunicipality, + #[strum(serialize = "29")] + GjorcePetrovMunicipality, + #[strum(serialize = "19")] + GostivarMunicipality, + #[strum(serialize = "20")] + GradskoMunicipality, + #[strum(serialize = "85")] + GreaterSkopje, + #[strum(serialize = "34")] + IlindenMunicipality, + #[strum(serialize = "35")] + JegunovceMunicipality, + #[strum(serialize = "37")] + Karbinci, + #[strum(serialize = "38")] + KarposMunicipality, + #[strum(serialize = "36")] + KavadarciMunicipality, + #[strum(serialize = "39")] + KiselaVodaMunicipality, + #[strum(serialize = "40")] + KicevoMunicipality, + #[strum(serialize = "41")] + KonceMunicipality, + #[strum(serialize = "42")] + KocaniMunicipality, + #[strum(serialize = "43")] + KratovoMunicipality, + #[strum(serialize = "44")] + KrivaPalankaMunicipality, + #[strum(serialize = "45")] + KrivogastaniMunicipality, + #[strum(serialize = "46")] + KrusevoMunicipality, + #[strum(serialize = "47")] + KumanovoMunicipality, + #[strum(serialize = "48")] + LipkovoMunicipality, + #[strum(serialize = "49")] + LozovoMunicipality, + #[strum(serialize = "51")] + MakedonskaKamenicaMunicipality, + #[strum(serialize = "52")] + MakedonskiBrodMunicipality, + #[strum(serialize = "50")] + MavrovoAndRostusaMunicipality, + #[strum(serialize = "53")] + MogilaMunicipality, + #[strum(serialize = "54")] + NegotinoMunicipality, + #[strum(serialize = "55")] + NovaciMunicipality, + #[strum(serialize = "56")] + NovoSeloMunicipality, + #[strum(serialize = "58")] + OhridMunicipality, + #[strum(serialize = "57")] + OslomejMunicipality, + #[strum(serialize = "60")] + PehcevoMunicipality, + #[strum(serialize = "59")] + PetrovecMunicipality, + #[strum(serialize = "61")] + PlasnicaMunicipality, + #[strum(serialize = "62")] + PrilepMunicipality, + #[strum(serialize = "63")] + ProbistipMunicipality, + #[strum(serialize = "64")] + RadovisMunicipality, + #[strum(serialize = "65")] + RankovceMunicipality, + #[strum(serialize = "66")] + ResenMunicipality, + #[strum(serialize = "67")] + RosomanMunicipality, + #[strum(serialize = "68")] + SarajMunicipality, + #[strum(serialize = "70")] + SopisteMunicipality, + #[strum(serialize = "71")] + StaroNagoricaneMunicipality, + #[strum(serialize = "72")] + StrugaMunicipality, + #[strum(serialize = "73")] + StrumicaMunicipality, + #[strum(serialize = "74")] + StudenicaniMunicipality, + #[strum(serialize = "69")] + SvetiNikoleMunicipality, + #[strum(serialize = "75")] + TearceMunicipality, + #[strum(serialize = "76")] + TetovoMunicipality, + #[strum(serialize = "10")] + ValandovoMunicipality, + #[strum(serialize = "11")] + VasilevoMunicipality, + #[strum(serialize = "13")] + VelesMunicipality, + #[strum(serialize = "12")] + VevcaniMunicipality, + #[strum(serialize = "14")] + VinicaMunicipality, + #[strum(serialize = "15")] + VranesticaMunicipality, + #[strum(serialize = "16")] + VrapcisteMunicipality, + #[strum(serialize = "31")] + ZajasMunicipality, + #[strum(serialize = "32")] + ZelenikovoMunicipality, + #[strum(serialize = "33")] + ZrnovciMunicipality, + #[strum(serialize = "79")] + CairMunicipality, + #[strum(serialize = "80")] + CaskaMunicipality, + #[strum(serialize = "81")] + CesinovoOblesevoMunicipality, + #[strum(serialize = "82")] + CucerSandevoMunicipality, + #[strum(serialize = "83")] + StipMunicipality, + #[strum(serialize = "84")] + SutoOrizariMunicipality, + #[strum(serialize = "30")] + ZelinoMunicipality, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum NorwayStatesAbbreviation { + #[strum(serialize = "02")] + Akershus, + #[strum(serialize = "06")] + Buskerud, + #[strum(serialize = "20")] + Finnmark, + #[strum(serialize = "04")] + Hedmark, + #[strum(serialize = "12")] + Hordaland, + #[strum(serialize = "22")] + JanMayen, + #[strum(serialize = "15")] + MoreOgRomsdal, + #[strum(serialize = "17")] + NordTrondelag, + #[strum(serialize = "18")] + Nordland, + #[strum(serialize = "05")] + Oppland, + #[strum(serialize = "03")] + Oslo, + #[strum(serialize = "11")] + Rogaland, + #[strum(serialize = "14")] + SognOgFjordane, + #[strum(serialize = "21")] + Svalbard, + #[strum(serialize = "16")] + SorTrondelag, + #[strum(serialize = "08")] + Telemark, + #[strum(serialize = "19")] + Troms, + #[strum(serialize = "50")] + Trondelag, + #[strum(serialize = "10")] + VestAgder, + #[strum(serialize = "07")] + Vestfold, + #[strum(serialize = "01")] + Ostfold, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum PolandStatesAbbreviation { + #[strum(serialize = "WP")] + GreaterPolandVoivodeship, + #[strum(serialize = "KI")] + Kielce, + #[strum(serialize = "KP")] + KuyavianPomeranianVoivodeship, + #[strum(serialize = "MA")] + LesserPolandVoivodeship, + #[strum(serialize = "DS")] + LowerSilesianVoivodeship, + #[strum(serialize = "LU")] + LublinVoivodeship, + #[strum(serialize = "LB")] + LubuszVoivodeship, + #[strum(serialize = "MZ")] + MasovianVoivodeship, + #[strum(serialize = "OP")] + OpoleVoivodeship, + #[strum(serialize = "PK")] + PodkarpackieVoivodeship, + #[strum(serialize = "PD")] + PodlaskieVoivodeship, + #[strum(serialize = "PM")] + PomeranianVoivodeship, + #[strum(serialize = "SL")] + SilesianVoivodeship, + #[strum(serialize = "WN")] + WarmianMasurianVoivodeship, + #[strum(serialize = "ZP")] + WestPomeranianVoivodeship, + #[strum(serialize = "LD")] + LodzVoivodeship, + #[strum(serialize = "SK")] + SwietokrzyskieVoivodeship, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum PortugalStatesAbbreviation { + #[strum(serialize = "01")] + AveiroDistrict, + #[strum(serialize = "20")] + Azores, + #[strum(serialize = "02")] + BejaDistrict, + #[strum(serialize = "03")] + BragaDistrict, + #[strum(serialize = "04")] + BragancaDistrict, + #[strum(serialize = "05")] + CasteloBrancoDistrict, + #[strum(serialize = "06")] + CoimbraDistrict, + #[strum(serialize = "08")] + FaroDistrict, + #[strum(serialize = "09")] + GuardaDistrict, + #[strum(serialize = "10")] + LeiriaDistrict, + #[strum(serialize = "11")] + LisbonDistrict, + #[strum(serialize = "30")] + Madeira, + #[strum(serialize = "12")] + PortalegreDistrict, + #[strum(serialize = "13")] + PortoDistrict, + #[strum(serialize = "14")] + SantaremDistrict, + #[strum(serialize = "15")] + SetubalDistrict, + #[strum(serialize = "16")] + VianaDoCasteloDistrict, + #[strum(serialize = "17")] + VilaRealDistrict, + #[strum(serialize = "18")] + ViseuDistrict, + #[strum(serialize = "07")] + EvoraDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SpainStatesAbbreviation { + #[strum(serialize = "C")] + ACorunaProvince, + #[strum(serialize = "AB")] + AlbaceteProvince, + #[strum(serialize = "A")] + AlicanteProvince, + #[strum(serialize = "AL")] + AlmeriaProvince, + #[strum(serialize = "AN")] + Andalusia, + #[strum(serialize = "VI")] + ArabaAlava, + #[strum(serialize = "AR")] + Aragon, + #[strum(serialize = "BA")] + BadajozProvince, + #[strum(serialize = "PM")] + BalearicIslands, + #[strum(serialize = "B")] + BarcelonaProvince, + #[strum(serialize = "PV")] + BasqueCountry, + #[strum(serialize = "BI")] + Biscay, + #[strum(serialize = "BU")] + BurgosProvince, + #[strum(serialize = "CN")] + CanaryIslands, + #[strum(serialize = "S")] + Cantabria, + #[strum(serialize = "CS")] + CastellonProvince, + #[strum(serialize = "CL")] + CastileAndLeon, + #[strum(serialize = "CM")] + CastileLaMancha, + #[strum(serialize = "CT")] + Catalonia, + #[strum(serialize = "CE")] + Ceuta, + #[strum(serialize = "CR")] + CiudadRealProvince, + #[strum(serialize = "MD")] + CommunityOfMadrid, + #[strum(serialize = "CU")] + CuencaProvince, + #[strum(serialize = "CC")] + CaceresProvince, + #[strum(serialize = "CA")] + CadizProvince, + #[strum(serialize = "CO")] + CordobaProvince, + #[strum(serialize = "EX")] + Extremadura, + #[strum(serialize = "GA")] + Galicia, + #[strum(serialize = "SS")] + Gipuzkoa, + #[strum(serialize = "GI")] + GironaProvince, + #[strum(serialize = "GR")] + GranadaProvince, + #[strum(serialize = "GU")] + GuadalajaraProvince, + #[strum(serialize = "H")] + HuelvaProvince, + #[strum(serialize = "HU")] + HuescaProvince, + #[strum(serialize = "J")] + JaenProvince, + #[strum(serialize = "RI")] + LaRioja, + #[strum(serialize = "GC")] + LasPalmasProvince, + #[strum(serialize = "LE")] + LeonProvince, + #[strum(serialize = "L")] + LleidaProvince, + #[strum(serialize = "LU")] + LugoProvince, + #[strum(serialize = "M")] + MadridProvince, + #[strum(serialize = "ML")] + Melilla, + #[strum(serialize = "MU")] + MurciaProvince, + #[strum(serialize = "MA")] + MalagaProvince, + #[strum(serialize = "NC")] + Navarre, + #[strum(serialize = "OR")] + OurenseProvince, + #[strum(serialize = "P")] + PalenciaProvince, + #[strum(serialize = "PO")] + PontevedraProvince, + #[strum(serialize = "O")] + ProvinceOfAsturias, + #[strum(serialize = "AV")] + ProvinceOfAvila, + #[strum(serialize = "MC")] + RegionOfMurcia, + #[strum(serialize = "SA")] + SalamancaProvince, + #[strum(serialize = "TF")] + SantaCruzDeTenerifeProvince, + #[strum(serialize = "SG")] + SegoviaProvince, + #[strum(serialize = "SE")] + SevilleProvince, + #[strum(serialize = "SO")] + SoriaProvince, + #[strum(serialize = "T")] + TarragonaProvince, + #[strum(serialize = "TE")] + TeruelProvince, + #[strum(serialize = "TO")] + ToledoProvince, + #[strum(serialize = "V")] + ValenciaProvince, + #[strum(serialize = "VC")] + ValencianCommunity, + #[strum(serialize = "VA")] + ValladolidProvince, + #[strum(serialize = "ZA")] + ZamoraProvince, + #[strum(serialize = "Z")] + ZaragozaProvince, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SwitzerlandStatesAbbreviation { + #[strum(serialize = "AG")] + Aargau, + #[strum(serialize = "AR")] + AppenzellAusserrhoden, + #[strum(serialize = "AI")] + AppenzellInnerrhoden, + #[strum(serialize = "BL")] + BaselLandschaft, + #[strum(serialize = "FR")] + CantonOfFribourg, + #[strum(serialize = "GE")] + CantonOfGeneva, + #[strum(serialize = "JU")] + CantonOfJura, + #[strum(serialize = "LU")] + CantonOfLucerne, + #[strum(serialize = "NE")] + CantonOfNeuchatel, + #[strum(serialize = "SH")] + CantonOfSchaffhausen, + #[strum(serialize = "SO")] + CantonOfSolothurn, + #[strum(serialize = "SG")] + CantonOfStGallen, + #[strum(serialize = "VS")] + CantonOfValais, + #[strum(serialize = "VD")] + CantonOfVaud, + #[strum(serialize = "ZG")] + CantonOfZug, + #[strum(serialize = "GL")] + Glarus, + #[strum(serialize = "GR")] + Graubunden, + #[strum(serialize = "NW")] + Nidwalden, + #[strum(serialize = "OW")] + Obwalden, + #[strum(serialize = "SZ")] + Schwyz, + #[strum(serialize = "TG")] + Thurgau, + #[strum(serialize = "TI")] + Ticino, + #[strum(serialize = "UR")] + Uri, + #[strum(serialize = "BE")] + CantonOfBern, + #[strum(serialize = "ZH")] + CantonOfZurich, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum UnitedKingdomStatesAbbreviation { + #[strum(serialize = "ABE")] + AberdeenCity, + #[strum(serialize = "ABD")] + Aberdeenshire, + #[strum(serialize = "ANS")] + Angus, + #[strum(serialize = "ANN")] + AntrimAndNewtownabbey, + #[strum(serialize = "AND")] + ArdsAndNorthDown, + #[strum(serialize = "AGB")] + ArgyllAndBute, + #[strum(serialize = "ABC")] + ArmaghCityBanbridgeAndCraigavon, + #[strum(serialize = "BDG")] + BarkingAndDagenham, + #[strum(serialize = "BNE")] + Barnet, + #[strum(serialize = "BNS")] + Barnsley, + #[strum(serialize = "BAS")] + BathAndNorthEastSomerset, + #[strum(serialize = "BDF")] + Bedford, + #[strum(serialize = "BFS")] + BelfastCity, + #[strum(serialize = "BEX")] + Bexley, + #[strum(serialize = "BIR")] + Birmingham, + #[strum(serialize = "BBD")] + BlackburnWithDarwen, + #[strum(serialize = "BPL")] + Blackpool, + #[strum(serialize = "BGW")] + BlaenauGwent, + #[strum(serialize = "BOL")] + Bolton, + #[strum(serialize = "BCP")] + BournemouthChristchurchAndPoole, + #[strum(serialize = "BRC")] + BracknellForest, + #[strum(serialize = "BRD")] + Bradford, + #[strum(serialize = "BEN")] + Brent, + #[strum(serialize = "BGE")] + Bridgend, + #[strum(serialize = "BNH")] + BrightonAndHove, + #[strum(serialize = "BST")] + BristolCityOf, + #[strum(serialize = "BRY")] + Bromley, + #[strum(serialize = "BKM")] + Buckinghamshire, + #[strum(serialize = "BUR")] + Bury, + #[strum(serialize = "CAY")] + Caerphilly, + #[strum(serialize = "CLD")] + Calderdale, + #[strum(serialize = "CAM")] + Cambridgeshire, + #[strum(serialize = "CMD")] + Camden, + #[strum(serialize = "CRF")] + Cardiff, + #[strum(serialize = "CMN")] + Carmarthenshire, + #[strum(serialize = "CCG")] + CausewayCoastAndGlens, + #[strum(serialize = "CBF")] + CentralBedfordshire, + #[strum(serialize = "CGN")] + Ceredigion, + #[strum(serialize = "CHE")] + CheshireEast, + #[strum(serialize = "CHW")] + CheshireWestAndChester, + #[strum(serialize = "CLK")] + Clackmannanshire, + #[strum(serialize = "CWY")] + Conwy, + #[strum(serialize = "CON")] + Cornwall, + #[strum(serialize = "COV")] + Coventry, + #[strum(serialize = "CRY")] + Croydon, + #[strum(serialize = "CMA")] + Cumbria, + #[strum(serialize = "DAL")] + Darlington, + #[strum(serialize = "DEN")] + Denbighshire, + #[strum(serialize = "DER")] + Derby, + #[strum(serialize = "DBY")] + Derbyshire, + #[strum(serialize = "DRS")] + DerryAndStrabane, + #[strum(serialize = "DEV")] + Devon, + #[strum(serialize = "DNC")] + Doncaster, + #[strum(serialize = "DOR")] + Dorset, + #[strum(serialize = "DUD")] + Dudley, + #[strum(serialize = "DGY")] + DumfriesAndGalloway, + #[strum(serialize = "DND")] + DundeeCity, + #[strum(serialize = "DUR")] + DurhamCounty, + #[strum(serialize = "EAL")] + Ealing, + #[strum(serialize = "EAY")] + EastAyrshire, + #[strum(serialize = "EDU")] + EastDunbartonshire, + #[strum(serialize = "ELN")] + EastLothian, + #[strum(serialize = "ERW")] + EastRenfrewshire, + #[strum(serialize = "ERY")] + EastRidingOfYorkshire, + #[strum(serialize = "ESX")] + EastSussex, + #[strum(serialize = "EDH")] + EdinburghCityOf, + #[strum(serialize = "ELS")] + EileanSiar, + #[strum(serialize = "ENF")] + Enfield, + #[strum(serialize = "ESS")] + Essex, + #[strum(serialize = "FAL")] + Falkirk, + #[strum(serialize = "FMO")] + FermanaghAndOmagh, + #[strum(serialize = "FIF")] + Fife, + #[strum(serialize = "FLN")] + Flintshire, + #[strum(serialize = "GAT")] + Gateshead, + #[strum(serialize = "GLG")] + GlasgowCity, + #[strum(serialize = "GLS")] + Gloucestershire, + #[strum(serialize = "GRE")] + Greenwich, + #[strum(serialize = "GWN")] + Gwynedd, + #[strum(serialize = "HCK")] + Hackney, + #[strum(serialize = "HAL")] + Halton, + #[strum(serialize = "HMF")] + HammersmithAndFulham, + #[strum(serialize = "HAM")] + Hampshire, + #[strum(serialize = "HRY")] + Haringey, + #[strum(serialize = "HRW")] + Harrow, + #[strum(serialize = "HPL")] + Hartlepool, + #[strum(serialize = "HAV")] + Havering, + #[strum(serialize = "HEF")] + Herefordshire, + #[strum(serialize = "HRT")] + Hertfordshire, + #[strum(serialize = "HLD")] + Highland, + #[strum(serialize = "HIL")] + Hillingdon, + #[strum(serialize = "HNS")] + Hounslow, + #[strum(serialize = "IVC")] + Inverclyde, + #[strum(serialize = "AGY")] + IsleOfAnglesey, + #[strum(serialize = "IOW")] + IsleOfWight, + #[strum(serialize = "IOS")] + IslesOfScilly, + #[strum(serialize = "ISL")] + Islington, + #[strum(serialize = "KEC")] + KensingtonAndChelsea, + #[strum(serialize = "KEN")] + Kent, + #[strum(serialize = "KHL")] + KingstonUponHull, + #[strum(serialize = "KTT")] + KingstonUponThames, + #[strum(serialize = "KIR")] + Kirklees, + #[strum(serialize = "KWL")] + Knowsley, + #[strum(serialize = "LBH")] + Lambeth, + #[strum(serialize = "LAN")] + Lancashire, + #[strum(serialize = "LDS")] + Leeds, + #[strum(serialize = "LCE")] + Leicester, + #[strum(serialize = "LEC")] + Leicestershire, + #[strum(serialize = "LEW")] + Lewisham, + #[strum(serialize = "LIN")] + Lincolnshire, + #[strum(serialize = "LBC")] + LisburnAndCastlereagh, + #[strum(serialize = "LIV")] + Liverpool, + #[strum(serialize = "LND")] + LondonCityOf, + #[strum(serialize = "LUT")] + Luton, + #[strum(serialize = "MAN")] + Manchester, + #[strum(serialize = "MDW")] + Medway, + #[strum(serialize = "MTY")] + MerthyrTydfil, + #[strum(serialize = "MRT")] + Merton, + #[strum(serialize = "MEA")] + MidAndEastAntrim, + #[strum(serialize = "MUL")] + MidUlster, + #[strum(serialize = "MDB")] + Middlesbrough, + #[strum(serialize = "MLN")] + Midlothian, + #[strum(serialize = "MIK")] + MiltonKeynes, + #[strum(serialize = "MON")] + Monmouthshire, + #[strum(serialize = "MRY")] + Moray, + #[strum(serialize = "NTL")] + NeathPortTalbot, + #[strum(serialize = "NET")] + NewcastleUponTyne, + #[strum(serialize = "NWM")] + Newham, + #[strum(serialize = "NWP")] + Newport, + #[strum(serialize = "NMD")] + NewryMourneAndDown, + #[strum(serialize = "NFK")] + Norfolk, + #[strum(serialize = "NAY")] + NorthAyrshire, + #[strum(serialize = "NEL")] + NorthEastLincolnshire, + #[strum(serialize = "NLK")] + NorthLanarkshire, + #[strum(serialize = "NLN")] + NorthLincolnshire, + #[strum(serialize = "NSM")] + NorthSomerset, + #[strum(serialize = "NTY")] + NorthTyneside, + #[strum(serialize = "NYK")] + NorthYorkshire, + #[strum(serialize = "NTH")] + Northamptonshire, + #[strum(serialize = "NBL")] + Northumberland, + #[strum(serialize = "NGM")] + Nottingham, + #[strum(serialize = "NTT")] + Nottinghamshire, + #[strum(serialize = "OLD")] + Oldham, + #[strum(serialize = "ORK")] + OrkneyIslands, + #[strum(serialize = "OXF")] + Oxfordshire, + #[strum(serialize = "PEM")] + Pembrokeshire, + #[strum(serialize = "PKN")] + PerthAndKinross, + #[strum(serialize = "PTE")] + Peterborough, + #[strum(serialize = "PLY")] + Plymouth, + #[strum(serialize = "POR")] + Portsmouth, + #[strum(serialize = "POW")] + Powys, + #[strum(serialize = "RDG")] + Reading, + #[strum(serialize = "RDB")] + Redbridge, + #[strum(serialize = "RCC")] + RedcarAndCleveland, + #[strum(serialize = "RFW")] + Renfrewshire, + #[strum(serialize = "RCT")] + RhonddaCynonTaff, + #[strum(serialize = "RIC")] + RichmondUponThames, + #[strum(serialize = "RCH")] + Rochdale, + #[strum(serialize = "ROT")] + Rotherham, + #[strum(serialize = "RUT")] + Rutland, + #[strum(serialize = "SLF")] + Salford, + #[strum(serialize = "SAW")] + Sandwell, + #[strum(serialize = "SCB")] + ScottishBorders, + #[strum(serialize = "SFT")] + Sefton, + #[strum(serialize = "SHF")] + Sheffield, + #[strum(serialize = "ZET")] + ShetlandIslands, + #[strum(serialize = "SHR")] + Shropshire, + #[strum(serialize = "SLG")] + Slough, + #[strum(serialize = "SOL")] + Solihull, + #[strum(serialize = "SOM")] + Somerset, + #[strum(serialize = "SAY")] + SouthAyrshire, + #[strum(serialize = "SGC")] + SouthGloucestershire, + #[strum(serialize = "SLK")] + SouthLanarkshire, + #[strum(serialize = "STY")] + SouthTyneside, + #[strum(serialize = "STH")] + Southampton, + #[strum(serialize = "SOS")] + SouthendOnSea, + #[strum(serialize = "SWK")] + Southwark, + #[strum(serialize = "SHN")] + StHelens, + #[strum(serialize = "STS")] + Staffordshire, + #[strum(serialize = "STG")] + Stirling, + #[strum(serialize = "SKP")] + Stockport, + #[strum(serialize = "STT")] + StocktonOnTees, + #[strum(serialize = "STE")] + StokeOnTrent, + #[strum(serialize = "SFK")] + Suffolk, + #[strum(serialize = "SND")] + Sunderland, + #[strum(serialize = "SRY")] + Surrey, + #[strum(serialize = "STN")] + Sutton, + #[strum(serialize = "SWA")] + Swansea, + #[strum(serialize = "SWD")] + Swindon, + #[strum(serialize = "TAM")] + Tameside, + #[strum(serialize = "TFW")] + TelfordAndWrekin, + #[strum(serialize = "THR")] + Thurrock, + #[strum(serialize = "TOB")] + Torbay, + #[strum(serialize = "TOF")] + Torfaen, + #[strum(serialize = "TWH")] + TowerHamlets, + #[strum(serialize = "TRF")] + Trafford, + #[strum(serialize = "VGL")] + ValeOfGlamorgan, + #[strum(serialize = "WKF")] + Wakefield, + #[strum(serialize = "WLL")] + Walsall, + #[strum(serialize = "WFT")] + WalthamForest, + #[strum(serialize = "WND")] + Wandsworth, + #[strum(serialize = "WRT")] + Warrington, + #[strum(serialize = "WAR")] + Warwickshire, + #[strum(serialize = "WBK")] + WestBerkshire, + #[strum(serialize = "WDU")] + WestDunbartonshire, + #[strum(serialize = "WLN")] + WestLothian, + #[strum(serialize = "WSX")] + WestSussex, + #[strum(serialize = "WSM")] + Westminster, + #[strum(serialize = "WGN")] + Wigan, + #[strum(serialize = "WIL")] + Wiltshire, + #[strum(serialize = "WNM")] + WindsorAndMaidenhead, + #[strum(serialize = "WRL")] + Wirral, + #[strum(serialize = "WOK")] + Wokingham, + #[strum(serialize = "WLV")] + Wolverhampton, + #[strum(serialize = "WOR")] + Worcestershire, + #[strum(serialize = "WRX")] + Wrexham, + #[strum(serialize = "YOR")] + York, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum RomaniaStatesAbbreviation { + #[strum(serialize = "AB")] + Alba, + #[strum(serialize = "AR")] + AradCounty, + #[strum(serialize = "AG")] + Arges, + #[strum(serialize = "BC")] + BacauCounty, + #[strum(serialize = "BH")] + BihorCounty, + #[strum(serialize = "BN")] + BistritaNasaudCounty, + #[strum(serialize = "BT")] + BotosaniCounty, + #[strum(serialize = "BR")] + Braila, + #[strum(serialize = "BV")] + BrasovCounty, + #[strum(serialize = "B")] + Bucharest, + #[strum(serialize = "BZ")] + BuzauCounty, + #[strum(serialize = "CS")] + CarasSeverinCounty, + #[strum(serialize = "CJ")] + ClujCounty, + #[strum(serialize = "CT")] + ConstantaCounty, + #[strum(serialize = "CV")] + CovasnaCounty, + #[strum(serialize = "CL")] + CalarasiCounty, + #[strum(serialize = "DJ")] + DoljCounty, + #[strum(serialize = "DB")] + DambovitaCounty, + #[strum(serialize = "GL")] + GalatiCounty, + #[strum(serialize = "GR")] + GiurgiuCounty, + #[strum(serialize = "GJ")] + GorjCounty, + #[strum(serialize = "HR")] + HarghitaCounty, + #[strum(serialize = "HD")] + HunedoaraCounty, + #[strum(serialize = "IL")] + IalomitaCounty, + #[strum(serialize = "IS")] + IasiCounty, + #[strum(serialize = "IF")] + IlfovCounty, + #[strum(serialize = "MH")] + MehedintiCounty, + #[strum(serialize = "MM")] + MuresCounty, + #[strum(serialize = "NT")] + NeamtCounty, + #[strum(serialize = "OT")] + OltCounty, + #[strum(serialize = "PH")] + PrahovaCounty, + #[strum(serialize = "SM")] + SatuMareCounty, + #[strum(serialize = "SB")] + SibiuCounty, + #[strum(serialize = "SV")] + SuceavaCounty, + #[strum(serialize = "SJ")] + SalajCounty, + #[strum(serialize = "TR")] + TeleormanCounty, + #[strum(serialize = "TM")] + TimisCounty, + #[strum(serialize = "TL")] + TulceaCounty, + #[strum(serialize = "VS")] + VasluiCounty, + #[strum(serialize = "VN")] + VranceaCounty, + #[strum(serialize = "VL")] + ValceaCounty, +} + #[derive( Clone, Copy, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index b14abec9d3a..4ed91f5ffdb 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -6,7 +6,21 @@ use api_models::payouts::PayoutVendorAccountDetails; use base64::Engine; use common_enums::{ enums, - enums::{AttemptStatus, CanadaStatesAbbreviation, FutureUsage, UsStatesAbbreviation}, + enums::{ + AlbaniaStatesAbbreviation, AndorraStatesAbbreviation, AttemptStatus, + AustriaStatesAbbreviation, BelarusStatesAbbreviation, + BosniaAndHerzegovinaStatesAbbreviation, BulgariaStatesAbbreviation, + CanadaStatesAbbreviation, CroatiaStatesAbbreviation, CzechRepublicStatesAbbreviation, + DenmarkStatesAbbreviation, FinlandStatesAbbreviation, FranceStatesAbbreviation, + FutureUsage, GermanyStatesAbbreviation, GreeceStatesAbbreviation, + HungaryStatesAbbreviation, IcelandStatesAbbreviation, IrelandStatesAbbreviation, + ItalyStatesAbbreviation, LatviaStatesAbbreviation, LiechtensteinStatesAbbreviation, + LithuaniaStatesAbbreviation, MaltaStatesAbbreviation, MoldovaStatesAbbreviation, + MonacoStatesAbbreviation, MontenegroStatesAbbreviation, NetherlandsStatesAbbreviation, + NorthMacedoniaStatesAbbreviation, NorwayStatesAbbreviation, PolandStatesAbbreviation, + PortugalStatesAbbreviation, RomaniaStatesAbbreviation, SpainStatesAbbreviation, + SwitzerlandStatesAbbreviation, UnitedKingdomStatesAbbreviation, UsStatesAbbreviation, + }, }; use common_utils::{ consts::BASE64_ENGINE, @@ -1344,6 +1358,111 @@ impl AddressDetailsData for AddressDetails { api_models::enums::CountryAlpha2::CA => Ok(Secret::new( CanadaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), )), + api_models::enums::CountryAlpha2::AL => Ok(Secret::new( + AlbaniaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::AD => Ok(Secret::new( + AndorraStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::AT => Ok(Secret::new( + AustriaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::BY => Ok(Secret::new( + BelarusStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::BA => Ok(Secret::new( + BosniaAndHerzegovinaStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::BG => Ok(Secret::new( + BulgariaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::HR => Ok(Secret::new( + CroatiaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::CZ => Ok(Secret::new( + CzechRepublicStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::DK => Ok(Secret::new( + DenmarkStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::FI => Ok(Secret::new( + FinlandStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::FR => Ok(Secret::new( + FranceStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::DE => Ok(Secret::new( + GermanyStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::GR => Ok(Secret::new( + GreeceStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::HU => Ok(Secret::new( + HungaryStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::IS => Ok(Secret::new( + IcelandStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::IE => Ok(Secret::new( + IrelandStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::LV => Ok(Secret::new( + LatviaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::IT => Ok(Secret::new( + ItalyStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::LI => Ok(Secret::new( + LiechtensteinStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::LT => Ok(Secret::new( + LithuaniaStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::MT => Ok(Secret::new( + MaltaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::MD => Ok(Secret::new( + MoldovaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::MC => Ok(Secret::new( + MonacoStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::ME => Ok(Secret::new( + MontenegroStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::NL => Ok(Secret::new( + NetherlandsStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::MK => Ok(Secret::new( + NorthMacedoniaStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::NO => Ok(Secret::new( + NorwayStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::PL => Ok(Secret::new( + PolandStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::PT => Ok(Secret::new( + PortugalStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::ES => Ok(Secret::new( + SpainStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), + )), + api_models::enums::CountryAlpha2::CH => Ok(Secret::new( + SwitzerlandStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), + api_models::enums::CountryAlpha2::GB => Ok(Secret::new( + UnitedKingdomStatesAbbreviation::foreign_try_from(state.peek().to_string())? + .to_string(), + )), _ => Ok(state.clone()), } } @@ -2339,6 +2458,1952 @@ impl ForeignTryFrom for CanadaStatesAbbreviation { } } +impl ForeignTryFrom for PolandStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "PolandStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "greater poland voivodeship" => Ok(Self::GreaterPolandVoivodeship), + "kielce" => Ok(Self::Kielce), + "kuyavian pomeranian voivodeship" => Ok(Self::KuyavianPomeranianVoivodeship), + "lesser poland voivodeship" => Ok(Self::LesserPolandVoivodeship), + "lower silesian voivodeship" => Ok(Self::LowerSilesianVoivodeship), + "lublin voivodeship" => Ok(Self::LublinVoivodeship), + "lubusz voivodeship" => Ok(Self::LubuszVoivodeship), + "masovian voivodeship" => Ok(Self::MasovianVoivodeship), + "opole voivodeship" => Ok(Self::OpoleVoivodeship), + "podkarpackie voivodeship" => Ok(Self::PodkarpackieVoivodeship), + "podlaskie voivodeship" => Ok(Self::PodlaskieVoivodeship), + "pomeranian voivodeship" => Ok(Self::PomeranianVoivodeship), + "silesian voivodeship" => Ok(Self::SilesianVoivodeship), + "warmian masurian voivodeship" => Ok(Self::WarmianMasurianVoivodeship), + "west pomeranian voivodeship" => Ok(Self::WestPomeranianVoivodeship), + "lodz voivodeship" => Ok(Self::LodzVoivodeship), + "swietokrzyskie voivodeship" => Ok(Self::SwietokrzyskieVoivodeship), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for FranceStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "FranceStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "alo" => Ok(Self::Alo), + "alsace" => Ok(Self::Alsace), + "aquitaine" => Ok(Self::Aquitaine), + "auvergne" => Ok(Self::Auvergne), + "auvergne rhone alpes" => Ok(Self::AuvergneRhoneAlpes), + "bourgogne franche comte" => Ok(Self::BourgogneFrancheComte), + "brittany" => Ok(Self::Brittany), + "burgundy" => Ok(Self::Burgundy), + "centre val de loire" => Ok(Self::CentreValDeLoire), + "champagne ardenne" => Ok(Self::ChampagneArdenne), + "corsica" => Ok(Self::Corsica), + "franche comte" => Ok(Self::FrancheComte), + "french guiana" => Ok(Self::FrenchGuiana), + "french polynesia" => Ok(Self::FrenchPolynesia), + "grand est" => Ok(Self::GrandEst), + "guadeloupe" => Ok(Self::Guadeloupe), + "hauts de france" => Ok(Self::HautsDeFrance), + "ile de france" => Ok(Self::IleDeFrance), + "normandy" => Ok(Self::Normandy), + "nouvelle aquitaine" => Ok(Self::NouvelleAquitaine), + "occitania" => Ok(Self::Occitania), + "paris" => Ok(Self::Paris), + "pays de la loire" => Ok(Self::PaysDeLaLoire), + "provence alpes cote d azur" => Ok(Self::ProvenceAlpesCoteDAzur), + "reunion" => Ok(Self::Reunion), + "saint barthelemy" => Ok(Self::SaintBarthelemy), + "saint martin" => Ok(Self::SaintMartin), + "saint pierre and miquelon" => Ok(Self::SaintPierreAndMiquelon), + "upper normandy" => Ok(Self::UpperNormandy), + "wallis and futuna" => Ok(Self::WallisAndFutuna), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for GermanyStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "GermanyStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "baden wurttemberg" => Ok(Self::BadenWurttemberg), + "bavaria" => Ok(Self::Bavaria), + "berlin" => Ok(Self::Berlin), + "brandenburg" => Ok(Self::Brandenburg), + "bremen" => Ok(Self::Bremen), + "hamburg" => Ok(Self::Hamburg), + "hesse" => Ok(Self::Hesse), + "lower saxony" => Ok(Self::LowerSaxony), + "mecklenburg vorpommern" => Ok(Self::MecklenburgVorpommern), + "north rhine westphalia" => Ok(Self::NorthRhineWestphalia), + "rhineland palatinate" => Ok(Self::RhinelandPalatinate), + "saarland" => Ok(Self::Saarland), + "saxony" => Ok(Self::Saxony), + "saxony anhalt" => Ok(Self::SaxonyAnhalt), + "schleswig holstein" => Ok(Self::SchleswigHolstein), + "thuringia" => Ok(Self::Thuringia), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SpainStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "SpainStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "a coruna province" => Ok(Self::ACorunaProvince), + "albacete province" => Ok(Self::AlbaceteProvince), + "alicante province" => Ok(Self::AlicanteProvince), + "almeria province" => Ok(Self::AlmeriaProvince), + "andalusia" => Ok(Self::Andalusia), + "araba alava" => Ok(Self::ArabaAlava), + "aragon" => Ok(Self::Aragon), + "badajoz province" => Ok(Self::BadajozProvince), + "balearic islands" => Ok(Self::BalearicIslands), + "barcelona province" => Ok(Self::BarcelonaProvince), + "basque country" => Ok(Self::BasqueCountry), + "biscay" => Ok(Self::Biscay), + "burgos province" => Ok(Self::BurgosProvince), + "canary islands" => Ok(Self::CanaryIslands), + "cantabria" => Ok(Self::Cantabria), + "castellon province" => Ok(Self::CastellonProvince), + "castile and leon" => Ok(Self::CastileAndLeon), + "castile la mancha" => Ok(Self::CastileLaMancha), + "catalonia" => Ok(Self::Catalonia), + "ceuta" => Ok(Self::Ceuta), + "ciudad real province" => Ok(Self::CiudadRealProvince), + "community of madrid" => Ok(Self::CommunityOfMadrid), + "cuenca province" => Ok(Self::CuencaProvince), + "caceres province" => Ok(Self::CaceresProvince), + "cadiz province" => Ok(Self::CadizProvince), + "cordoba province" => Ok(Self::CordobaProvince), + "extremadura" => Ok(Self::Extremadura), + "galicia" => Ok(Self::Galicia), + "gipuzkoa" => Ok(Self::Gipuzkoa), + "girona province" => Ok(Self::GironaProvince), + "granada province" => Ok(Self::GranadaProvince), + "guadalajara province" => Ok(Self::GuadalajaraProvince), + "huelva province" => Ok(Self::HuelvaProvince), + "huesca province" => Ok(Self::HuescaProvince), + "jaen province" => Ok(Self::JaenProvince), + "la rioja" => Ok(Self::LaRioja), + "las palmas province" => Ok(Self::LasPalmasProvince), + "leon province" => Ok(Self::LeonProvince), + "lleida province" => Ok(Self::LleidaProvince), + "lugo province" => Ok(Self::LugoProvince), + "madrid province" => Ok(Self::MadridProvince), + "melilla" => Ok(Self::Melilla), + "murcia province" => Ok(Self::MurciaProvince), + "malaga province" => Ok(Self::MalagaProvince), + "navarre" => Ok(Self::Navarre), + "ourense province" => Ok(Self::OurenseProvince), + "palencia province" => Ok(Self::PalenciaProvince), + "pontevedra province" => Ok(Self::PontevedraProvince), + "province of asturias" => Ok(Self::ProvinceOfAsturias), + "province of avila" => Ok(Self::ProvinceOfAvila), + "region of murcia" => Ok(Self::RegionOfMurcia), + "salamanca province" => Ok(Self::SalamancaProvince), + "santa cruz de tenerife province" => Ok(Self::SantaCruzDeTenerifeProvince), + "segovia province" => Ok(Self::SegoviaProvince), + "seville province" => Ok(Self::SevilleProvince), + "soria province" => Ok(Self::SoriaProvince), + "tarragona province" => Ok(Self::TarragonaProvince), + "teruel province" => Ok(Self::TeruelProvince), + "toledo province" => Ok(Self::ToledoProvince), + "valencia province" => Ok(Self::ValenciaProvince), + "valencian community" => Ok(Self::ValencianCommunity), + "valladolid province" => Ok(Self::ValladolidProvince), + "zamora province" => Ok(Self::ZamoraProvince), + "zaragoza province" => Ok(Self::ZaragozaProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for ItalyStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "ItalyStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "abruzzo" => Ok(Self::Abruzzo), + "aosta valley" => Ok(Self::AostaValley), + "apulia" => Ok(Self::Apulia), + "basilicata" => Ok(Self::Basilicata), + "benevento province" => Ok(Self::BeneventoProvince), + "calabria" => Ok(Self::Calabria), + "campania" => Ok(Self::Campania), + "emilia romagna" => Ok(Self::EmiliaRomagna), + "friuli venezia giulia" => Ok(Self::FriuliVeneziaGiulia), + "lazio" => Ok(Self::Lazio), + "liguria" => Ok(Self::Liguria), + "lombardy" => Ok(Self::Lombardy), + "marche" => Ok(Self::Marche), + "molise" => Ok(Self::Molise), + "piedmont" => Ok(Self::Piedmont), + "sardinia" => Ok(Self::Sardinia), + "sicily" => Ok(Self::Sicily), + "trentino south tyrol" => Ok(Self::TrentinoSouthTyrol), + "tuscany" => Ok(Self::Tuscany), + "umbria" => Ok(Self::Umbria), + "veneto" => Ok(Self::Veneto), + "agrigento" => Ok(Self::Agrigento), + "caltanissetta" => Ok(Self::Caltanissetta), + "enna" => Ok(Self::Enna), + "ragusa" => Ok(Self::Ragusa), + "siracusa" => Ok(Self::Siracusa), + "trapani" => Ok(Self::Trapani), + "bari" => Ok(Self::Bari), + "bologna" => Ok(Self::Bologna), + "cagliari" => Ok(Self::Cagliari), + "catania" => Ok(Self::Catania), + "florence" => Ok(Self::Florence), + "genoa" => Ok(Self::Genoa), + "messina" => Ok(Self::Messina), + "milan" => Ok(Self::Milan), + "naples" => Ok(Self::Naples), + "palermo" => Ok(Self::Palermo), + "reggio calabria" => Ok(Self::ReggioCalabria), + "rome" => Ok(Self::Rome), + "turin" => Ok(Self::Turin), + "venice" => Ok(Self::Venice), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for NorwayStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "NorwayStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "akershus" => Ok(Self::Akershus), + "buskerud" => Ok(Self::Buskerud), + "finnmark" => Ok(Self::Finnmark), + "hedmark" => Ok(Self::Hedmark), + "hordaland" => Ok(Self::Hordaland), + "janmayen" => Ok(Self::JanMayen), + "nordtrondelag" => Ok(Self::NordTrondelag), + "nordland" => Ok(Self::Nordland), + "oppland" => Ok(Self::Oppland), + "oslo" => Ok(Self::Oslo), + "rogaland" => Ok(Self::Rogaland), + "sognogfjordane" => Ok(Self::SognOgFjordane), + "svalbard" => Ok(Self::Svalbard), + "sortrondelag" => Ok(Self::SorTrondelag), + "telemark" => Ok(Self::Telemark), + "troms" => Ok(Self::Troms), + "trondelag" => Ok(Self::Trondelag), + "vestagder" => Ok(Self::VestAgder), + "vestfold" => Ok(Self::Vestfold), + "ostfold" => Ok(Self::Ostfold), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for AlbaniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "AlbaniaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "berat" => Ok(Self::Berat), + "diber" => Ok(Self::Diber), + "durres" => Ok(Self::Durres), + "elbasan" => Ok(Self::Elbasan), + "fier" => Ok(Self::Fier), + "gjirokaster" => Ok(Self::Gjirokaster), + "korce" => Ok(Self::Korce), + "kukes" => Ok(Self::Kukes), + "lezhe" => Ok(Self::Lezhe), + "shkoder" => Ok(Self::Shkoder), + "tirane" => Ok(Self::Tirane), + "vlore" => Ok(Self::Vlore), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for AndorraStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "AndorraStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "andorra la vella" => Ok(Self::AndorraLaVella), + "canillo" => Ok(Self::Canillo), + "encamp" => Ok(Self::Encamp), + "escaldes engordany" => Ok(Self::EscaldesEngordany), + "la massana" => Ok(Self::LaMassana), + "ordino" => Ok(Self::Ordino), + "sant julia de loria" => Ok(Self::SantJuliaDeLoria), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for AustriaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "AustriaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "burgenland" => Ok(Self::Burgenland), + "carinthia" => Ok(Self::Carinthia), + "lower austria" => Ok(Self::LowerAustria), + "salzburg" => Ok(Self::Salzburg), + "styria" => Ok(Self::Styria), + "tyrol" => Ok(Self::Tyrol), + "upper austria" => Ok(Self::UpperAustria), + "vienna" => Ok(Self::Vienna), + "vorarlberg" => Ok(Self::Vorarlberg), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for RomaniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "RomaniaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "alba" => Ok(Self::Alba), + "arad county" => Ok(Self::AradCounty), + "arges" => Ok(Self::Arges), + "bacau county" => Ok(Self::BacauCounty), + "bihor county" => Ok(Self::BihorCounty), + "bistrita nasaud county" => Ok(Self::BistritaNasaudCounty), + "botosani county" => Ok(Self::BotosaniCounty), + "braila" => Ok(Self::Braila), + "brasov county" => Ok(Self::BrasovCounty), + "bucharest" => Ok(Self::Bucharest), + "buzau county" => Ok(Self::BuzauCounty), + "caras severin county" => Ok(Self::CarasSeverinCounty), + "cluj county" => Ok(Self::ClujCounty), + "constanta county" => Ok(Self::ConstantaCounty), + "covasna county" => Ok(Self::CovasnaCounty), + "calarasi county" => Ok(Self::CalarasiCounty), + "dolj county" => Ok(Self::DoljCounty), + "dambovita county" => Ok(Self::DambovitaCounty), + "galati county" => Ok(Self::GalatiCounty), + "giurgiu county" => Ok(Self::GiurgiuCounty), + "gorj county" => Ok(Self::GorjCounty), + "harghita county" => Ok(Self::HarghitaCounty), + "hunedoara county" => Ok(Self::HunedoaraCounty), + "ialomita county" => Ok(Self::IalomitaCounty), + "iasi county" => Ok(Self::IasiCounty), + "ilfov county" => Ok(Self::IlfovCounty), + "mehedinti county" => Ok(Self::MehedintiCounty), + "mures county" => Ok(Self::MuresCounty), + "neamt county" => Ok(Self::NeamtCounty), + "olt county" => Ok(Self::OltCounty), + "prahova county" => Ok(Self::PrahovaCounty), + "satu mare county" => Ok(Self::SatuMareCounty), + "sibiu county" => Ok(Self::SibiuCounty), + "suceava county" => Ok(Self::SuceavaCounty), + "salaj county" => Ok(Self::SalajCounty), + "teleorman county" => Ok(Self::TeleormanCounty), + "timis county" => Ok(Self::TimisCounty), + "tulcea county" => Ok(Self::TulceaCounty), + "vaslui county" => Ok(Self::VasluiCounty), + "vrancea county" => Ok(Self::VranceaCounty), + "valcea county" => Ok(Self::ValceaCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for PortugalStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "PortugalStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "aveiro district" => Ok(Self::AveiroDistrict), + "azores" => Ok(Self::Azores), + "beja district" => Ok(Self::BejaDistrict), + "braga district" => Ok(Self::BragaDistrict), + "braganca district" => Ok(Self::BragancaDistrict), + "castelo branco district" => Ok(Self::CasteloBrancoDistrict), + "coimbra district" => Ok(Self::CoimbraDistrict), + "faro district" => Ok(Self::FaroDistrict), + "guarda district" => Ok(Self::GuardaDistrict), + "leiria district" => Ok(Self::LeiriaDistrict), + "lisbon district" => Ok(Self::LisbonDistrict), + "madeira" => Ok(Self::Madeira), + "portalegre district" => Ok(Self::PortalegreDistrict), + "porto district" => Ok(Self::PortoDistrict), + "santarem district" => Ok(Self::SantaremDistrict), + "setubal district" => Ok(Self::SetubalDistrict), + "viana do castelo district" => Ok(Self::VianaDoCasteloDistrict), + "vila real district" => Ok(Self::VilaRealDistrict), + "viseu district" => Ok(Self::ViseuDistrict), + "evora district" => Ok(Self::EvoraDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SwitzerlandStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "SwitzerlandStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "aargau" => Ok(Self::Aargau), + "appenzell ausserrhoden" => Ok(Self::AppenzellAusserrhoden), + "appenzell innerrhoden" => Ok(Self::AppenzellInnerrhoden), + "basel landschaft" => Ok(Self::BaselLandschaft), + "canton of fribourg" => Ok(Self::CantonOfFribourg), + "canton of geneva" => Ok(Self::CantonOfGeneva), + "canton of jura" => Ok(Self::CantonOfJura), + "canton of lucerne" => Ok(Self::CantonOfLucerne), + "canton of neuchatel" => Ok(Self::CantonOfNeuchatel), + "canton of schaffhausen" => Ok(Self::CantonOfSchaffhausen), + "canton of solothurn" => Ok(Self::CantonOfSolothurn), + "canton of st gallen" => Ok(Self::CantonOfStGallen), + "canton of valais" => Ok(Self::CantonOfValais), + "canton of vaud" => Ok(Self::CantonOfVaud), + "canton of zug" => Ok(Self::CantonOfZug), + "glarus" => Ok(Self::Glarus), + "graubunden" => Ok(Self::Graubunden), + "nidwalden" => Ok(Self::Nidwalden), + "obwalden" => Ok(Self::Obwalden), + "schwyz" => Ok(Self::Schwyz), + "thurgau" => Ok(Self::Thurgau), + "ticino" => Ok(Self::Ticino), + "uri" => Ok(Self::Uri), + "canton of bern" => Ok(Self::CantonOfBern), + "canton of zurich" => Ok(Self::CantonOfZurich), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for NorthMacedoniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "NorthMacedoniaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "aerodrom municipality" => Ok(Self::AerodromMunicipality), + "aracinovo municipality" => Ok(Self::AracinovoMunicipality), + "berovo municipality" => Ok(Self::BerovoMunicipality), + "bitola municipality" => Ok(Self::BitolaMunicipality), + "bogdanci municipality" => Ok(Self::BogdanciMunicipality), + "bogovinje municipality" => Ok(Self::BogovinjeMunicipality), + "bosilovo municipality" => Ok(Self::BosilovoMunicipality), + "brvenica municipality" => Ok(Self::BrvenicaMunicipality), + "butel municipality" => Ok(Self::ButelMunicipality), + "centar municipality" => Ok(Self::CentarMunicipality), + "centar zupa municipality" => Ok(Self::CentarZupaMunicipality), + "debarca municipality" => Ok(Self::DebarcaMunicipality), + "delcevo municipality" => Ok(Self::DelcevoMunicipality), + "demir hisar municipality" => Ok(Self::DemirHisarMunicipality), + "demir kapija municipality" => Ok(Self::DemirKapijaMunicipality), + "dojran municipality" => Ok(Self::DojranMunicipality), + "dolneni municipality" => Ok(Self::DolneniMunicipality), + "drugovo municipality" => Ok(Self::DrugovoMunicipality), + "gazi baba municipality" => Ok(Self::GaziBabaMunicipality), + "gevgelija municipality" => Ok(Self::GevgelijaMunicipality), + "gjorce petrov municipality" => Ok(Self::GjorcePetrovMunicipality), + "gostivar municipality" => Ok(Self::GostivarMunicipality), + "gradsko municipality" => Ok(Self::GradskoMunicipality), + "greater skopje" => Ok(Self::GreaterSkopje), + "ilinden municipality" => Ok(Self::IlindenMunicipality), + "jegunovce municipality" => Ok(Self::JegunovceMunicipality), + "karbinci" => Ok(Self::Karbinci), + "karpos municipality" => Ok(Self::KarposMunicipality), + "kavadarci municipality" => Ok(Self::KavadarciMunicipality), + "kisela voda municipality" => Ok(Self::KiselaVodaMunicipality), + "kicevo municipality" => Ok(Self::KicevoMunicipality), + "konce municipality" => Ok(Self::KonceMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for MontenegroStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "MontenegroStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "andrijevica municipality" => Ok(Self::AndrijevicaMunicipality), + "bar municipality" => Ok(Self::BarMunicipality), + "berane municipality" => Ok(Self::BeraneMunicipality), + "bijelo polje municipality" => Ok(Self::BijeloPoljeMunicipality), + "budva municipality" => Ok(Self::BudvaMunicipality), + "danilovgrad municipality" => Ok(Self::DanilovgradMunicipality), + "gusinje municipality" => Ok(Self::GusinjeMunicipality), + "kolasin municipality" => Ok(Self::KolasinMunicipality), + "kotor municipality" => Ok(Self::KotorMunicipality), + "mojkovac municipality" => Ok(Self::MojkovacMunicipality), + "niksic municipality" => Ok(Self::NiksicMunicipality), + "old royal capital cetinje" => Ok(Self::OldRoyalCapitalCetinje), + "petnjica municipality" => Ok(Self::PetnjicaMunicipality), + "plav municipality" => Ok(Self::PlavMunicipality), + "pljevlja municipality" => Ok(Self::PljevljaMunicipality), + "pluzine municipality" => Ok(Self::PluzineMunicipality), + "podgorica municipality" => Ok(Self::PodgoricaMunicipality), + "rozaje municipality" => Ok(Self::RozajeMunicipality), + "tivat municipality" => Ok(Self::TivatMunicipality), + "ulcinj municipality" => Ok(Self::UlcinjMunicipality), + "savnik municipality" => Ok(Self::SavnikMunicipality), + "zabljak municipality" => Ok(Self::ZabljakMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for MonacoStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "MonacoStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "monaco" => Ok(Self::Monaco), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for NetherlandsStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "NetherlandsStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "bonaire" => Ok(Self::Bonaire), + "drenthe" => Ok(Self::Drenthe), + "flevoland" => Ok(Self::Flevoland), + "friesland" => Ok(Self::Friesland), + "gelderland" => Ok(Self::Gelderland), + "groningen" => Ok(Self::Groningen), + "limburg" => Ok(Self::Limburg), + "north brabant" => Ok(Self::NorthBrabant), + "north holland" => Ok(Self::NorthHolland), + "overijssel" => Ok(Self::Overijssel), + "saba" => Ok(Self::Saba), + "sint eustatius" => Ok(Self::SintEustatius), + "south holland" => Ok(Self::SouthHolland), + "utrecht" => Ok(Self::Utrecht), + "zeeland" => Ok(Self::Zeeland), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for MoldovaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "MoldovaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "anenii noi district" => Ok(Self::AneniiNoiDistrict), + "basarabeasca district" => Ok(Self::BasarabeascaDistrict), + "bender municipality" => Ok(Self::BenderMunicipality), + "briceni district" => Ok(Self::BriceniDistrict), + "balti municipality" => Ok(Self::BaltiMunicipality), + "cahul district" => Ok(Self::CahulDistrict), + "cantemir district" => Ok(Self::CantemirDistrict), + "chisinau municipality" => Ok(Self::ChisinauMunicipality), + "cimislia district" => Ok(Self::CimisliaDistrict), + "criuleni district" => Ok(Self::CriuleniDistrict), + "calarasi district" => Ok(Self::CalarasiDistrict), + "causeni district" => Ok(Self::CauseniDistrict), + "donduseni district" => Ok(Self::DonduseniDistrict), + "drochia district" => Ok(Self::DrochiaDistrict), + "dubasari district" => Ok(Self::DubasariDistrict), + "edinet district" => Ok(Self::EdinetDistrict), + "floresti district" => Ok(Self::FlorestiDistrict), + "falesti district" => Ok(Self::FalestiDistrict), + "gagauzia" => Ok(Self::Gagauzia), + "glodeni district" => Ok(Self::GlodeniDistrict), + "hincesti district" => Ok(Self::HincestiDistrict), + "ialoveni district" => Ok(Self::IaloveniDistrict), + "nisporeni district" => Ok(Self::NisporeniDistrict), + "ocnita district" => Ok(Self::OcnitaDistrict), + "orhei district" => Ok(Self::OrheiDistrict), + "rezina district" => Ok(Self::RezinaDistrict), + "riscani district" => Ok(Self::RiscaniDistrict), + "soroca district" => Ok(Self::SorocaDistrict), + "straseni district" => Ok(Self::StraseniDistrict), + "singerei district" => Ok(Self::SingereiDistrict), + "taraclia district" => Ok(Self::TaracliaDistrict), + "telenesti district" => Ok(Self::TelenestiDistrict), + "transnistria autonomous territorial unit" => { + Ok(Self::TransnistriaAutonomousTerritorialUnit) + } + "ungheni district" => Ok(Self::UngheniDistrict), + "soldanesti district" => Ok(Self::SoldanestiDistrict), + "stefan voda district" => Ok(Self::StefanVodaDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for LithuaniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "LithuaniaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "akmene district municipality" => Ok(Self::AkmeneDistrictMunicipality), + "alytus city municipality" => Ok(Self::AlytusCityMunicipality), + "alytus county" => Ok(Self::AlytusCounty), + "alytus district municipality" => Ok(Self::AlytusDistrictMunicipality), + "birstonas municipality" => Ok(Self::BirstonasMunicipality), + "birzai district municipality" => Ok(Self::BirzaiDistrictMunicipality), + "druskininkai municipality" => Ok(Self::DruskininkaiMunicipality), + "elektrenai municipality" => Ok(Self::ElektrenaiMunicipality), + "ignalina district municipality" => Ok(Self::IgnalinaDistrictMunicipality), + "jonava district municipality" => Ok(Self::JonavaDistrictMunicipality), + "joniskis district municipality" => Ok(Self::JoniskisDistrictMunicipality), + "jurbarkas district municipality" => Ok(Self::JurbarkasDistrictMunicipality), + "kaisiadorys district municipality" => { + Ok(Self::KaisiadorysDistrictMunicipality) + } + "kalvarija municipality" => Ok(Self::KalvarijaMunicipality), + "kaunas city municipality" => Ok(Self::KaunasCityMunicipality), + "kaunas county" => Ok(Self::KaunasCounty), + "kaunas district municipality" => Ok(Self::KaunasDistrictMunicipality), + "kazlu ruda municipality" => Ok(Self::KazluRudaMunicipality), + "kelme district municipality" => Ok(Self::KelmeDistrictMunicipality), + "klaipeda city municipality" => Ok(Self::KlaipedaCityMunicipality), + "klaipeda county" => Ok(Self::KlaipedaCounty), + "klaipeda district municipality" => Ok(Self::KlaipedaDistrictMunicipality), + "kretinga district municipality" => Ok(Self::KretingaDistrictMunicipality), + "kupiskis district municipality" => Ok(Self::KupiskisDistrictMunicipality), + "kedainiai district municipality" => Ok(Self::KedainiaiDistrictMunicipality), + "lazdijai district municipality" => Ok(Self::LazdijaiDistrictMunicipality), + "marijampole county" => Ok(Self::MarijampoleCounty), + "marijampole municipality" => Ok(Self::MarijampoleMunicipality), + "mazeikiai district municipality" => Ok(Self::MazeikiaiDistrictMunicipality), + "moletai district municipality" => Ok(Self::MoletaiDistrictMunicipality), + "neringa municipality" => Ok(Self::NeringaMunicipality), + "pagegiai municipality" => Ok(Self::PagegiaiMunicipality), + "pakruojis district municipality" => Ok(Self::PakruojisDistrictMunicipality), + "palanga city municipality" => Ok(Self::PalangaCityMunicipality), + "panevezys city municipality" => Ok(Self::PanevezysCityMunicipality), + "panevezys county" => Ok(Self::PanevezysCounty), + "panevezys district municipality" => Ok(Self::PanevezysDistrictMunicipality), + "pasvalys district municipality" => Ok(Self::PasvalysDistrictMunicipality), + "plunge district municipality" => Ok(Self::PlungeDistrictMunicipality), + "prienai district municipality" => Ok(Self::PrienaiDistrictMunicipality), + "radviliskis district municipality" => { + Ok(Self::RadviliskisDistrictMunicipality) + } + "raseiniai district municipality" => Ok(Self::RaseiniaiDistrictMunicipality), + "rietavas municipality" => Ok(Self::RietavasMunicipality), + "rokiskis district municipality" => Ok(Self::RokiskisDistrictMunicipality), + "skuodas district municipality" => Ok(Self::SkuodasDistrictMunicipality), + "taurage county" => Ok(Self::TaurageCounty), + "taurage district municipality" => Ok(Self::TaurageDistrictMunicipality), + "telsiai county" => Ok(Self::TelsiaiCounty), + "telsiai district municipality" => Ok(Self::TelsiaiDistrictMunicipality), + "trakai district municipality" => Ok(Self::TrakaiDistrictMunicipality), + "ukmerge district municipality" => Ok(Self::UkmergeDistrictMunicipality), + "utena county" => Ok(Self::UtenaCounty), + "utena district municipality" => Ok(Self::UtenaDistrictMunicipality), + "varena district municipality" => Ok(Self::VarenaDistrictMunicipality), + "vilkaviskis district municipality" => { + Ok(Self::VilkaviskisDistrictMunicipality) + } + "vilnius city municipality" => Ok(Self::VilniusCityMunicipality), + "vilnius county" => Ok(Self::VilniusCounty), + "vilnius district municipality" => Ok(Self::VilniusDistrictMunicipality), + "visaginas municipality" => Ok(Self::VisaginasMunicipality), + "zarasai district municipality" => Ok(Self::ZarasaiDistrictMunicipality), + "sakiai district municipality" => Ok(Self::SakiaiDistrictMunicipality), + "salcininkai district municipality" => { + Ok(Self::SalcininkaiDistrictMunicipality) + } + "siauliai city municipality" => Ok(Self::SiauliaiCityMunicipality), + "siauliai county" => Ok(Self::SiauliaiCounty), + "siauliai district municipality" => Ok(Self::SiauliaiDistrictMunicipality), + "silale district municipality" => Ok(Self::SilaleDistrictMunicipality), + "silute district municipality" => Ok(Self::SiluteDistrictMunicipality), + "sirvintos district municipality" => Ok(Self::SirvintosDistrictMunicipality), + "svencionys district municipality" => Ok(Self::SvencionysDistrictMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for LiechtensteinStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "LiechtensteinStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "balzers" => Ok(Self::Balzers), + "eschen" => Ok(Self::Eschen), + "gamprin" => Ok(Self::Gamprin), + "mauren" => Ok(Self::Mauren), + "planken" => Ok(Self::Planken), + "ruggell" => Ok(Self::Ruggell), + "schaan" => Ok(Self::Schaan), + "schellenberg" => Ok(Self::Schellenberg), + "triesen" => Ok(Self::Triesen), + "triesenberg" => Ok(Self::Triesenberg), + "vaduz" => Ok(Self::Vaduz), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for LatviaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "LatviaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "aglona municipality" => Ok(Self::AglonaMunicipality), + "aizkraukle municipality" => Ok(Self::AizkraukleMunicipality), + "aizpute municipality" => Ok(Self::AizputeMunicipality), + "akniiste municipality" => Ok(Self::AknīsteMunicipality), + "aloja municipality" => Ok(Self::AlojaMunicipality), + "alsunga municipality" => Ok(Self::AlsungaMunicipality), + "aluksne municipality" => Ok(Self::AlūksneMunicipality), + "amata municipality" => Ok(Self::AmataMunicipality), + "ape municipality" => Ok(Self::ApeMunicipality), + "auce municipality" => Ok(Self::AuceMunicipality), + "babite municipality" => Ok(Self::BabīteMunicipality), + "baldone municipality" => Ok(Self::BaldoneMunicipality), + "baltinava municipality" => Ok(Self::BaltinavaMunicipality), + "balvi municipality" => Ok(Self::BalviMunicipality), + "bauska municipality" => Ok(Self::BauskaMunicipality), + "beverina municipality" => Ok(Self::BeverīnaMunicipality), + "broceni municipality" => Ok(Self::BrocēniMunicipality), + "burtnieki municipality" => Ok(Self::BurtniekiMunicipality), + "carnikava municipality" => Ok(Self::CarnikavaMunicipality), + "cesvaine municipality" => Ok(Self::CesvaineMunicipality), + "cibla municipality" => Ok(Self::CiblaMunicipality), + "cesis municipality" => Ok(Self::CēsisMunicipality), + "dagda municipality" => Ok(Self::DagdaMunicipality), + "daugavpils" => Ok(Self::Daugavpils), + "daugavpils municipality" => Ok(Self::DaugavpilsMunicipality), + "dobele municipality" => Ok(Self::DobeleMunicipality), + "dundaga municipality" => Ok(Self::DundagaMunicipality), + "durbe municipality" => Ok(Self::DurbeMunicipality), + "engure municipality" => Ok(Self::EngureMunicipality), + "garkalne municipality" => Ok(Self::GarkalneMunicipality), + "grobina municipality" => Ok(Self::GrobiņaMunicipality), + "gulbene municipality" => Ok(Self::GulbeneMunicipality), + "iecava municipality" => Ok(Self::IecavaMunicipality), + "ikskile municipality" => Ok(Self::IkšķileMunicipality), + "ilukste municipality" => Ok(Self::IlūksteMunicipality), + "incukalns municipality" => Ok(Self::InčukalnsMunicipality), + "jaunjelgava municipality" => Ok(Self::JaunjelgavaMunicipality), + "jaunpiebalga municipality" => Ok(Self::JaunpiebalgaMunicipality), + "jaunpils municipality" => Ok(Self::JaunpilsMunicipality), + "jelgava" => Ok(Self::Jelgava), + "jelgava municipality" => Ok(Self::JelgavaMunicipality), + "jekabpils" => Ok(Self::Jēkabpils), + "jekabpils municipality" => Ok(Self::JēkabpilsMunicipality), + "jurmala" => Ok(Self::Jūrmala), + "kandava municipality" => Ok(Self::KandavaMunicipality), + "koceni municipality" => Ok(Self::KocēniMunicipality), + "koknese municipality" => Ok(Self::KokneseMunicipality), + "krimulda municipality" => Ok(Self::KrimuldaMunicipality), + "kustpils municipality" => Ok(Self::KrustpilsMunicipality), + "kraslava municipality" => Ok(Self::KrāslavaMunicipality), + "kuldiga municipality" => Ok(Self::KuldīgaMunicipality), + "karsava municipality" => Ok(Self::KārsavaMunicipality), + "lielvarde municipality" => Ok(Self::LielvārdeMunicipality), + "liepaja" => Ok(Self::Liepāja), + "limbazi municipality" => Ok(Self::LimbažiMunicipality), + "lubana municipality" => Ok(Self::LubānaMunicipality), + "ludza municipality" => Ok(Self::LudzaMunicipality), + "ligatne municipality" => Ok(Self::LīgatneMunicipality), + "livani municipality" => Ok(Self::LīvāniMunicipality), + "madona municipality" => Ok(Self::MadonaMunicipality), + "mazsalaca municipality" => Ok(Self::MazsalacaMunicipality), + "malpils municipality" => Ok(Self::MālpilsMunicipality), + "marupe municipality" => Ok(Self::MārupeMunicipality), + "mersrags municipality" => Ok(Self::MērsragsMunicipality), + "naukseni municipality" => Ok(Self::NaukšēniMunicipality), + "nereta municipality" => Ok(Self::NeretaMunicipality), + "nica municipality" => Ok(Self::NīcaMunicipality), + "ogre municipality" => Ok(Self::OgreMunicipality), + "olaine municipality" => Ok(Self::OlaineMunicipality), + "ozolnieki municipality" => Ok(Self::OzolniekiMunicipality), + "preili municipality" => Ok(Self::PreiļiMunicipality), + "priekule municipality" => Ok(Self::PriekuleMunicipality), + "priekuli municipality" => Ok(Self::PriekuļiMunicipality), + "pargauja municipality" => Ok(Self::PārgaujaMunicipality), + "pavilosta municipality" => Ok(Self::PāvilostaMunicipality), + "plavinas municipality" => Ok(Self::PļaviņasMunicipality), + "rauna municipality" => Ok(Self::RaunaMunicipality), + "riebini municipality" => Ok(Self::RiebiņiMunicipality), + "riga" => Ok(Self::Riga), + "roja municipality" => Ok(Self::RojaMunicipality), + "ropazi municipality" => Ok(Self::RopažiMunicipality), + "rucava municipality" => Ok(Self::RucavaMunicipality), + "rugaji municipality" => Ok(Self::RugājiMunicipality), + "rundale municipality" => Ok(Self::RundāleMunicipality), + "rezekne" => Ok(Self::Rēzekne), + "rezekne municipality" => Ok(Self::RēzekneMunicipality), + "rujiena municipality" => Ok(Self::RūjienaMunicipality), + "sala municipality" => Ok(Self::SalaMunicipality), + "salacgriva municipality" => Ok(Self::SalacgrīvaMunicipality), + "salaspils municipality" => Ok(Self::SalaspilsMunicipality), + "saldus municipality" => Ok(Self::SaldusMunicipality), + "saulkrasti municipality" => Ok(Self::SaulkrastiMunicipality), + "sigulda municipality" => Ok(Self::SiguldaMunicipality), + "skrunda municipality" => Ok(Self::SkrundaMunicipality), + "skriveri municipality" => Ok(Self::SkrīveriMunicipality), + "smiltene municipality" => Ok(Self::SmilteneMunicipality), + "stopini municipality" => Ok(Self::StopiņiMunicipality), + "strenci municipality" => Ok(Self::StrenčiMunicipality), + "seja municipality" => Ok(Self::SējaMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for MaltaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "MaltaStatesAbbreviation"); + + match state_abbreviation_check { + Ok(municipality) => Ok(municipality), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let municipality = binding.as_str(); + match municipality { + "attard" => Ok(Self::Attard), + "balzan" => Ok(Self::Balzan), + "birgu" => Ok(Self::Birgu), + "birkirkara" => Ok(Self::Birkirkara), + "birzebbuga" => Ok(Self::Birzebbuga), + "cospicua" => Ok(Self::Cospicua), + "dingli" => Ok(Self::Dingli), + "fgura" => Ok(Self::Fgura), + "floriana" => Ok(Self::Floriana), + "fontana" => Ok(Self::Fontana), + "gudja" => Ok(Self::Gudja), + "gzira" => Ok(Self::Gzira), + "ghajnsielem" => Ok(Self::Ghajnsielem), + "gharb" => Ok(Self::Gharb), + "gharghur" => Ok(Self::Gharghur), + "ghasri" => Ok(Self::Ghasri), + "ghaxaq" => Ok(Self::Ghaxaq), + "hamrun" => Ok(Self::Hamrun), + "iklin" => Ok(Self::Iklin), + "senglea" => Ok(Self::Senglea), + "kalkara" => Ok(Self::Kalkara), + "kercem" => Ok(Self::Kercem), + "kirkop" => Ok(Self::Kirkop), + "lija" => Ok(Self::Lija), + "luqa" => Ok(Self::Luqa), + "marsa" => Ok(Self::Marsa), + "marsaskala" => Ok(Self::Marsaskala), + "marsaxlokk" => Ok(Self::Marsaxlokk), + "mdina" => Ok(Self::Mdina), + "mellieha" => Ok(Self::Mellieha), + "mgarr" => Ok(Self::Mgarr), + "mosta" => Ok(Self::Mosta), + "mqabba" => Ok(Self::Mqabba), + "msida" => Ok(Self::Msida), + "mtarfa" => Ok(Self::Mtarfa), + "munxar" => Ok(Self::Munxar), + "nadur" => Ok(Self::Nadur), + "naxxar" => Ok(Self::Naxxar), + "paola" => Ok(Self::Paola), + "pembroke" => Ok(Self::Pembroke), + "pieta" => Ok(Self::Pieta), + "qala" => Ok(Self::Qala), + "qormi" => Ok(Self::Qormi), + "qrendi" => Ok(Self::Qrendi), + "victoria" => Ok(Self::Victoria), + "rabat" => Ok(Self::Rabat), + "st julians" => Ok(Self::StJulians), + "san gwann" => Ok(Self::SanGwann), + "saint lawrence" => Ok(Self::SaintLawrence), + "st pauls bay" => Ok(Self::StPaulsBay), + "sannat" => Ok(Self::Sannat), + "santa lucija" => Ok(Self::SantaLucija), + "santa venera" => Ok(Self::SantaVenera), + "siggiewi" => Ok(Self::Siggiewi), + "sliema" => Ok(Self::Sliema), + "swieqi" => Ok(Self::Swieqi), + "ta xbiex" => Ok(Self::TaXbiex), + "tarxien" => Ok(Self::Tarxien), + "valletta" => Ok(Self::Valletta), + "xaghra" => Ok(Self::Xaghra), + "xewkija" => Ok(Self::Xewkija), + "xghajra" => Ok(Self::Xghajra), + "zabbar" => Ok(Self::Zabbar), + "zebbug gozo" => Ok(Self::ZebbugGozo), + "zebbug malta" => Ok(Self::ZebbugMalta), + "zejtun" => Ok(Self::Zejtun), + "zurrieq" => Ok(Self::Zurrieq), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for BelarusStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "BelarusStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "brest region" => Ok(Self::BrestRegion), + "gomel region" => Ok(Self::GomelRegion), + "grodno region" => Ok(Self::GrodnoRegion), + "minsk" => Ok(Self::Minsk), + "minsk region" => Ok(Self::MinskRegion), + "mogilev region" => Ok(Self::MogilevRegion), + "vitebsk region" => Ok(Self::VitebskRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for IrelandStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "IrelandStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "connacht" => Ok(Self::Connacht), + "county carlow" => Ok(Self::CountyCarlow), + "county cavan" => Ok(Self::CountyCavan), + "county clare" => Ok(Self::CountyClare), + "county cork" => Ok(Self::CountyCork), + "county donegal" => Ok(Self::CountyDonegal), + "county dublin" => Ok(Self::CountyDublin), + "county galway" => Ok(Self::CountyGalway), + "county kerry" => Ok(Self::CountyKerry), + "county kildare" => Ok(Self::CountyKildare), + "county kilkenny" => Ok(Self::CountyKilkenny), + "county laois" => Ok(Self::CountyLaois), + "county limerick" => Ok(Self::CountyLimerick), + "county longford" => Ok(Self::CountyLongford), + "county louth" => Ok(Self::CountyLouth), + "county mayo" => Ok(Self::CountyMayo), + "county meath" => Ok(Self::CountyMeath), + "county monaghan" => Ok(Self::CountyMonaghan), + "county offaly" => Ok(Self::CountyOffaly), + "county roscommon" => Ok(Self::CountyRoscommon), + "county sligo" => Ok(Self::CountySligo), + "county tipperary" => Ok(Self::CountyTipperary), + "county waterford" => Ok(Self::CountyWaterford), + "county westmeath" => Ok(Self::CountyWestmeath), + "county wexford" => Ok(Self::CountyWexford), + "county wicklow" => Ok(Self::CountyWicklow), + "leinster" => Ok(Self::Leinster), + "munster" => Ok(Self::Munster), + "ulster" => Ok(Self::Ulster), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for IcelandStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "IcelandStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "capital region" => Ok(Self::CapitalRegion), + "eastern region" => Ok(Self::EasternRegion), + "northeastern region" => Ok(Self::NortheasternRegion), + "northwestern region" => Ok(Self::NorthwesternRegion), + "southern peninsula region" => Ok(Self::SouthernPeninsulaRegion), + "southern region" => Ok(Self::SouthernRegion), + "western region" => Ok(Self::WesternRegion), + "westfjords" => Ok(Self::Westfjords), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for HungaryStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "HungaryStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "baranya county" => Ok(Self::BaranyaCounty), + "borsod abauj zemplen county" => Ok(Self::BorsodAbaujZemplenCounty), + "budapest" => Ok(Self::Budapest), + "bacs kiskun county" => Ok(Self::BacsKiskunCounty), + "bekes county" => Ok(Self::BekesCounty), + "bekescsaba" => Ok(Self::Bekescsaba), + "csongrad county" => Ok(Self::CsongradCounty), + "debrecen" => Ok(Self::Debrecen), + "dunaujvaros" => Ok(Self::Dunaujvaros), + "eger" => Ok(Self::Eger), + "fejer county" => Ok(Self::FejerCounty), + "gyor" => Ok(Self::Gyor), + "gyor moson sopron county" => Ok(Self::GyorMosonSopronCounty), + "hajdu bihar county" => Ok(Self::HajduBiharCounty), + "heves county" => Ok(Self::HevesCounty), + "hodmezovasarhely" => Ok(Self::Hodmezovasarhely), + "jasz nagykun szolnok county" => Ok(Self::JaszNagykunSzolnokCounty), + "kaposvar" => Ok(Self::Kaposvar), + "kecskemet" => Ok(Self::Kecskemet), + "miskolc" => Ok(Self::Miskolc), + "nagykanizsa" => Ok(Self::Nagykanizsa), + "nyiregyhaza" => Ok(Self::Nyiregyhaza), + "nograd county" => Ok(Self::NogradCounty), + "pest county" => Ok(Self::PestCounty), + "pecs" => Ok(Self::Pecs), + "salgotarjan" => Ok(Self::Salgotarjan), + "somogy county" => Ok(Self::SomogyCounty), + "sopron" => Ok(Self::Sopron), + "szabolcs szatmar bereg county" => Ok(Self::SzabolcsSzatmarBeregCounty), + "szeged" => Ok(Self::Szeged), + "szekszard" => Ok(Self::Szekszard), + "szolnok" => Ok(Self::Szolnok), + "szombathely" => Ok(Self::Szombathely), + "szekesfehervar" => Ok(Self::Szekesfehervar), + "tatabanya" => Ok(Self::Tatabanya), + "tolna county" => Ok(Self::TolnaCounty), + "vas county" => Ok(Self::VasCounty), + "veszprem" => Ok(Self::Veszprem), + "veszprem county" => Ok(Self::VeszpremCounty), + "zala county" => Ok(Self::ZalaCounty), + "zalaegerszeg" => Ok(Self::Zalaegerszeg), + "erd" => Ok(Self::Erd), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for GreeceStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "GreeceStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "achaea regional unit" => Ok(Self::AchaeaRegionalUnit), + "aetolia acarnania regional unit" => Ok(Self::AetoliaAcarnaniaRegionalUnit), + "arcadia prefecture" => Ok(Self::ArcadiaPrefecture), + "argolis regional unit" => Ok(Self::ArgolisRegionalUnit), + "attica region" => Ok(Self::AtticaRegion), + "boeotia regional unit" => Ok(Self::BoeotiaRegionalUnit), + "central greece region" => Ok(Self::CentralGreeceRegion), + "central macedonia" => Ok(Self::CentralMacedonia), + "chania regional unit" => Ok(Self::ChaniaRegionalUnit), + "corfu prefecture" => Ok(Self::CorfuPrefecture), + "corinthia regional unit" => Ok(Self::CorinthiaRegionalUnit), + "crete region" => Ok(Self::CreteRegion), + "drama regional unit" => Ok(Self::DramaRegionalUnit), + "east attica regional unit" => Ok(Self::EastAtticaRegionalUnit), + "east macedonia and thrace" => Ok(Self::EastMacedoniaAndThrace), + "epirus region" => Ok(Self::EpirusRegion), + "euboea" => Ok(Self::Euboea), + "grevena prefecture" => Ok(Self::GrevenaPrefecture), + "imathia regional unit" => Ok(Self::ImathiaRegionalUnit), + "ioannina regional unit" => Ok(Self::IoanninaRegionalUnit), + "ionian islands region" => Ok(Self::IonianIslandsRegion), + "karditsa regional unit" => Ok(Self::KarditsaRegionalUnit), + "kastoria regional unit" => Ok(Self::KastoriaRegionalUnit), + "kefalonia prefecture" => Ok(Self::KefaloniaPrefecture), + "kilkis regional unit" => Ok(Self::KilkisRegionalUnit), + "kozani prefecture" => Ok(Self::KozaniPrefecture), + "laconia" => Ok(Self::Laconia), + "larissa prefecture" => Ok(Self::LarissaPrefecture), + "lefkada regional unit" => Ok(Self::LefkadaRegionalUnit), + "pella regional unit" => Ok(Self::PellaRegionalUnit), + "peloponnese region" => Ok(Self::PeloponneseRegion), + "phthiotis prefecture" => Ok(Self::PhthiotisPrefecture), + "preveza prefecture" => Ok(Self::PrevezaPrefecture), + "serres prefecture" => Ok(Self::SerresPrefecture), + "south aegean" => Ok(Self::SouthAegean), + "thessaloniki regional unit" => Ok(Self::ThessalonikiRegionalUnit), + "west greece region" => Ok(Self::WestGreeceRegion), + "west macedonia region" => Ok(Self::WestMacedoniaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for FinlandStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "FinlandStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "central finland" => Ok(Self::CentralFinland), + "central ostrobothnia" => Ok(Self::CentralOstrobothnia), + "eastern finland province" => Ok(Self::EasternFinlandProvince), + "finland proper" => Ok(Self::FinlandProper), + "kainuu" => Ok(Self::Kainuu), + "kymenlaakso" => Ok(Self::Kymenlaakso), + "lapland" => Ok(Self::Lapland), + "north karelia" => Ok(Self::NorthKarelia), + "northern ostrobothnia" => Ok(Self::NorthernOstrobothnia), + "northern savonia" => Ok(Self::NorthernSavonia), + "ostrobothnia" => Ok(Self::Ostrobothnia), + "oulu province" => Ok(Self::OuluProvince), + "pirkanmaa" => Ok(Self::Pirkanmaa), + "paijanne tavastia" => Ok(Self::PaijanneTavastia), + "satakunta" => Ok(Self::Satakunta), + "south karelia" => Ok(Self::SouthKarelia), + "southern ostrobothnia" => Ok(Self::SouthernOstrobothnia), + "southern savonia" => Ok(Self::SouthernSavonia), + "tavastia proper" => Ok(Self::TavastiaProper), + "uusimaa" => Ok(Self::Uusimaa), + "aland islands" => Ok(Self::AlandIslands), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for DenmarkStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "DenmarkStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "capital region of denmark" => Ok(Self::CapitalRegionOfDenmark), + "central denmark region" => Ok(Self::CentralDenmarkRegion), + "north denmark region" => Ok(Self::NorthDenmarkRegion), + "region zealand" => Ok(Self::RegionZealand), + "region of southern denmark" => Ok(Self::RegionOfSouthernDenmark), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for CzechRepublicStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "CzechRepublicStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "benesov district" => Ok(Self::BenesovDistrict), + "beroun district" => Ok(Self::BerounDistrict), + "blansko district" => Ok(Self::BlanskoDistrict), + "brno city district" => Ok(Self::BrnoCityDistrict), + "brno country district" => Ok(Self::BrnoCountryDistrict), + "bruntal district" => Ok(Self::BruntalDistrict), + "breclav district" => Ok(Self::BreclavDistrict), + "central bohemian region" => Ok(Self::CentralBohemianRegion), + "cheb district" => Ok(Self::ChebDistrict), + "chomutov district" => Ok(Self::ChomutovDistrict), + "chrudim district" => Ok(Self::ChrudimDistrict), + "domazlice district" => Ok(Self::DomazliceDistrict), + "decin district" => Ok(Self::DecinDistrict), + "frydek mistek district" => Ok(Self::FrydekMistekDistrict), + "havlickuv brod district" => Ok(Self::HavlickuvBrodDistrict), + "hodonin district" => Ok(Self::HodoninDistrict), + "horni pocernice" => Ok(Self::HorniPocernice), + "hradec kralove district" => Ok(Self::HradecKraloveDistrict), + "hradec kralove region" => Ok(Self::HradecKraloveRegion), + "jablonec nad nisou district" => Ok(Self::JablonecNadNisouDistrict), + "jesenik district" => Ok(Self::JesenikDistrict), + "jihlava district" => Ok(Self::JihlavaDistrict), + "jindrichuv hradec district" => Ok(Self::JindrichuvHradecDistrict), + "jicin district" => Ok(Self::JicinDistrict), + "karlovy vary district" => Ok(Self::KarlovyVaryDistrict), + "karlovy vary region" => Ok(Self::KarlovyVaryRegion), + "karvina district" => Ok(Self::KarvinaDistrict), + "kladno district" => Ok(Self::KladnoDistrict), + "klatovy district" => Ok(Self::KlatovyDistrict), + "kolin district" => Ok(Self::KolinDistrict), + "kromeriz district" => Ok(Self::KromerizDistrict), + "liberec district" => Ok(Self::LiberecDistrict), + "liberec region" => Ok(Self::LiberecRegion), + "litomerice district" => Ok(Self::LitomericeDistrict), + "louny district" => Ok(Self::LounyDistrict), + "mlada boleslav district" => Ok(Self::MladaBoleslavDistrict), + "moravian silesian region" => Ok(Self::MoravianSilesianRegion), + "most district" => Ok(Self::MostDistrict), + "melnik district" => Ok(Self::MelnikDistrict), + "novy jicin district" => Ok(Self::NovyJicinDistrict), + "nymburk district" => Ok(Self::NymburkDistrict), + "nachod district" => Ok(Self::NachodDistrict), + "olomouc district" => Ok(Self::OlomoucDistrict), + "olomouc region" => Ok(Self::OlomoucRegion), + "opava district" => Ok(Self::OpavaDistrict), + "ostrava city district" => Ok(Self::OstravaCityDistrict), + "pardubice district" => Ok(Self::PardubiceDistrict), + "pardubice region" => Ok(Self::PardubiceRegion), + "pelhrimov district" => Ok(Self::PelhrimovDistrict), + "plzen region" => Ok(Self::PlzenRegion), + "plzen city district" => Ok(Self::PlzenCityDistrict), + "plzen north district" => Ok(Self::PlzenNorthDistrict), + "plzen south district" => Ok(Self::PlzenSouthDistrict), + "prachatice district" => Ok(Self::PrachaticeDistrict), + "prague" => Ok(Self::Prague), + "prague1" => Ok(Self::Prague1), + "prague10" => Ok(Self::Prague10), + "prague11" => Ok(Self::Prague11), + "prague12" => Ok(Self::Prague12), + "prague13" => Ok(Self::Prague13), + "prague14" => Ok(Self::Prague14), + "prague15" => Ok(Self::Prague15), + "prague16" => Ok(Self::Prague16), + "prague2" => Ok(Self::Prague2), + "prague21" => Ok(Self::Prague21), + "prague3" => Ok(Self::Prague3), + "prague4" => Ok(Self::Prague4), + "prague5" => Ok(Self::Prague5), + "prague6" => Ok(Self::Prague6), + "prague7" => Ok(Self::Prague7), + "prague8" => Ok(Self::Prague8), + "prague9" => Ok(Self::Prague9), + "prague east district" => Ok(Self::PragueEastDistrict), + "prague west district" => Ok(Self::PragueWestDistrict), + "prostejov district" => Ok(Self::ProstejovDistrict), + "pisek district" => Ok(Self::PisekDistrict), + "prerov district" => Ok(Self::PrerovDistrict), + "pribram district" => Ok(Self::PribramDistrict), + "rakovnik district" => Ok(Self::RakovnikDistrict), + "rokycany district" => Ok(Self::RokycanyDistrict), + "rychnov nad kneznou district" => Ok(Self::RychnovNadKneznouDistrict), + "semily district" => Ok(Self::SemilyDistrict), + "sokolov district" => Ok(Self::SokolovDistrict), + "south bohemian region" => Ok(Self::SouthBohemianRegion), + "south moravian region" => Ok(Self::SouthMoravianRegion), + "strakonice district" => Ok(Self::StrakoniceDistrict), + "svitavy district" => Ok(Self::SvitavyDistrict), + "tachov district" => Ok(Self::TachovDistrict), + "teplice district" => Ok(Self::TepliceDistrict), + "trutnov district" => Ok(Self::TrutnovDistrict), + "tabor district" => Ok(Self::TaborDistrict), + "trebic district" => Ok(Self::TrebicDistrict), + "uherske hradiste district" => Ok(Self::UherskeHradisteDistrict), + "vsetin district" => Ok(Self::VsetinDistrict), + "vysocina region" => Ok(Self::VysocinaRegion), + "vyskov district" => Ok(Self::VyskovDistrict), + "zlin district" => Ok(Self::ZlinDistrict), + "zlin region" => Ok(Self::ZlinRegion), + "znojmo district" => Ok(Self::ZnojmoDistrict), + "usti nad labem district" => Ok(Self::UstiNadLabemDistrict), + "usti nad labem region" => Ok(Self::UstiNadLabemRegion), + "usti nad orlici district" => Ok(Self::UstiNadOrliciDistrict), + "ceska lipa district" => Ok(Self::CeskaLipaDistrict), + "ceske budejovice district" => Ok(Self::CeskeBudejoviceDistrict), + "cesky krumlov district" => Ok(Self::CeskyKrumlovDistrict), + "sumperk district" => Ok(Self::SumperkDistrict), + "zdar nad sazavou district" => Ok(Self::ZdarNadSazavouDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for CroatiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "CroatiaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "bjelovar bilogora county" => Ok(Self::BjelovarBilogoraCounty), + "brod posavina county" => Ok(Self::BrodPosavinaCounty), + "dubrovnik neretva county" => Ok(Self::DubrovnikNeretvaCounty), + "istria county" => Ok(Self::IstriaCounty), + "koprivnica krizevci county" => Ok(Self::KoprivnicaKrizevciCounty), + "krapina zagorje county" => Ok(Self::KrapinaZagorjeCounty), + "lika senj county" => Ok(Self::LikaSenjCounty), + "medimurje county" => Ok(Self::MedimurjeCounty), + "osijek baranja county" => Ok(Self::OsijekBaranjaCounty), + "pozega slavonian county" => Ok(Self::PozegaSlavoniaCounty), + "primorje gorski kotar county" => Ok(Self::PrimorjeGorskiKotarCounty), + "sisak moslavina county" => Ok(Self::SisakMoslavinaCounty), + "split dalmatia county" => Ok(Self::SplitDalmatiaCounty), + "varazdin county" => Ok(Self::VarazdinCounty), + "virovitica podravina county" => Ok(Self::ViroviticaPodravinaCounty), + "vukovar syrmia county" => Ok(Self::VukovarSyrmiaCounty), + "zadar county" => Ok(Self::ZadarCounty), + "zagreb" => Ok(Self::Zagreb), + "zagreb county" => Ok(Self::ZagrebCounty), + "sibenik knin county" => Ok(Self::SibenikKninCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for BulgariaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "BulgariaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "blagoevgrad province" => Ok(Self::BlagoevgradProvince), + "burgas province" => Ok(Self::BurgasProvince), + "dobrich province" => Ok(Self::DobrichProvince), + "gabrovo province" => Ok(Self::GabrovoProvince), + "haskovo province" => Ok(Self::HaskovoProvince), + "kardzhali province" => Ok(Self::KardzhaliProvince), + "kyustendil province" => Ok(Self::KyustendilProvince), + "lovech province" => Ok(Self::LovechProvince), + "montana province" => Ok(Self::MontanaProvince), + "pazardzhik province" => Ok(Self::PazardzhikProvince), + "pernik province" => Ok(Self::PernikProvince), + "pleven province" => Ok(Self::PlevenProvince), + "plovdiv province" => Ok(Self::PlovdivProvince), + "razgrad province" => Ok(Self::RazgradProvince), + "ruse province" => Ok(Self::RuseProvince), + "shumen" => Ok(Self::Shumen), + "silistra province" => Ok(Self::SilistraProvince), + "sliven province" => Ok(Self::SlivenProvince), + "smolyan province" => Ok(Self::SmolyanProvince), + "sofia city province" => Ok(Self::SofiaCityProvince), + "sofia province" => Ok(Self::SofiaProvince), + "stara zagora province" => Ok(Self::StaraZagoraProvince), + "targovishte province" => Ok(Self::TargovishteProvince), + "varna province" => Ok(Self::VarnaProvince), + "veliko tarnovo province" => Ok(Self::VelikoTarnovoProvince), + "vidin province" => Ok(Self::VidinProvince), + "vratsa province" => Ok(Self::VratsaProvince), + "yambol province" => Ok(Self::YambolProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for BosniaAndHerzegovinaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "BosniaAndHerzegovinaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "bosnian podrinje canton" => Ok(Self::BosnianPodrinjeCanton), + "brcko district" => Ok(Self::BrckoDistrict), + "canton 10" => Ok(Self::Canton10), + "central bosnia canton" => Ok(Self::CentralBosniaCanton), + "federation of bosnia and herzegovina" => { + Ok(Self::FederationOfBosniaAndHerzegovina) + } + "herzegovina neretva canton" => Ok(Self::HerzegovinaNeretvaCanton), + "posavina canton" => Ok(Self::PosavinaCanton), + "republika srpska" => Ok(Self::RepublikaSrpska), + "sarajevo canton" => Ok(Self::SarajevoCanton), + "tuzla canton" => Ok(Self::TuzlaCanton), + "una sana canton" => Ok(Self::UnaSanaCanton), + "west herzegovina canton" => Ok(Self::WestHerzegovinaCanton), + "zenica doboj canton" => Ok(Self::ZenicaDobojCanton), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for UnitedKingdomStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "UnitedKingdomStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "aberdeen city" => Ok(Self::AberdeenCity), + "aberdeenshire" => Ok(Self::Aberdeenshire), + "angus" => Ok(Self::Angus), + "antrim and newtownabbey" => Ok(Self::AntrimAndNewtownabbey), + "ards and north down" => Ok(Self::ArdsAndNorthDown), + "argyll and bute" => Ok(Self::ArgyllAndBute), + "armagh city banbridge and craigavon" => { + Ok(Self::ArmaghCityBanbridgeAndCraigavon) + } + "barking and dagenham" => Ok(Self::BarkingAndDagenham), + "barnet" => Ok(Self::Barnet), + "barnsley" => Ok(Self::Barnsley), + "bath and north east somerset" => Ok(Self::BathAndNorthEastSomerset), + "bedford" => Ok(Self::Bedford), + "belfast city" => Ok(Self::BelfastCity), + "bexley" => Ok(Self::Bexley), + "birmingham" => Ok(Self::Birmingham), + "blackburn with darwen" => Ok(Self::BlackburnWithDarwen), + "blackpool" => Ok(Self::Blackpool), + "blaenau gwent" => Ok(Self::BlaenauGwent), + "bolton" => Ok(Self::Bolton), + "bournemouth christchurch and poole" => { + Ok(Self::BournemouthChristchurchAndPoole) + } + "bracknell forest" => Ok(Self::BracknellForest), + "bradford" => Ok(Self::Bradford), + "brent" => Ok(Self::Brent), + "bridgend" => Ok(Self::Bridgend), + "brighton and hove" => Ok(Self::BrightonAndHove), + "bristol city of" => Ok(Self::BristolCityOf), + "bromley" => Ok(Self::Bromley), + "buckinghamshire" => Ok(Self::Buckinghamshire), + "bury" => Ok(Self::Bury), + "caerphilly" => Ok(Self::Caerphilly), + "calderdale" => Ok(Self::Calderdale), + "cambridgeshire" => Ok(Self::Cambridgeshire), + "camden" => Ok(Self::Camden), + "cardiff" => Ok(Self::Cardiff), + "carmarthenshire" => Ok(Self::Carmarthenshire), + "causeway coast and glens" => Ok(Self::CausewayCoastAndGlens), + "central bedfordshire" => Ok(Self::CentralBedfordshire), + "ceredigion" => Ok(Self::Ceredigion), + "cheshire east" => Ok(Self::CheshireEast), + "cheshire west and chester" => Ok(Self::CheshireWestAndChester), + "clackmannanshire" => Ok(Self::Clackmannanshire), + "conwy" => Ok(Self::Conwy), + "cornwall" => Ok(Self::Cornwall), + "coventry" => Ok(Self::Coventry), + "croydon" => Ok(Self::Croydon), + "cumbria" => Ok(Self::Cumbria), + "darlington" => Ok(Self::Darlington), + "denbighshire" => Ok(Self::Denbighshire), + "derby" => Ok(Self::Derby), + "derbyshire" => Ok(Self::Derbyshire), + "derry and strabane" => Ok(Self::DerryAndStrabane), + "devon" => Ok(Self::Devon), + "doncaster" => Ok(Self::Doncaster), + "dorset" => Ok(Self::Dorset), + "dudley" => Ok(Self::Dudley), + "dumfries and galloway" => Ok(Self::DumfriesAndGalloway), + "dundee city" => Ok(Self::DundeeCity), + "durham county" => Ok(Self::DurhamCounty), + "ealing" => Ok(Self::Ealing), + "east ayrshire" => Ok(Self::EastAyrshire), + "east dunbartonshire" => Ok(Self::EastDunbartonshire), + "east lothian" => Ok(Self::EastLothian), + "east renfrewshire" => Ok(Self::EastRenfrewshire), + "east riding of yorkshire" => Ok(Self::EastRidingOfYorkshire), + "east sussex" => Ok(Self::EastSussex), + "edinburgh city of" => Ok(Self::EdinburghCityOf), + "eilean siar" => Ok(Self::EileanSiar), + "enfield" => Ok(Self::Enfield), + "essex" => Ok(Self::Essex), + "falkirk" => Ok(Self::Falkirk), + "fermanagh and omagh" => Ok(Self::FermanaghAndOmagh), + "fife" => Ok(Self::Fife), + "flintshire" => Ok(Self::Flintshire), + "gateshead" => Ok(Self::Gateshead), + "glasgow city" => Ok(Self::GlasgowCity), + "gloucestershire" => Ok(Self::Gloucestershire), + "greenwich" => Ok(Self::Greenwich), + "gwynedd" => Ok(Self::Gwynedd), + "hackney" => Ok(Self::Hackney), + "halton" => Ok(Self::Halton), + "hammersmith and fulham" => Ok(Self::HammersmithAndFulham), + "hampshire" => Ok(Self::Hampshire), + "haringey" => Ok(Self::Haringey), + "harrow" => Ok(Self::Harrow), + "hartlepool" => Ok(Self::Hartlepool), + "havering" => Ok(Self::Havering), + "herefordshire" => Ok(Self::Herefordshire), + "hertfordshire" => Ok(Self::Hertfordshire), + "highland" => Ok(Self::Highland), + "hillingdon" => Ok(Self::Hillingdon), + "hounslow" => Ok(Self::Hounslow), + "inverclyde" => Ok(Self::Inverclyde), + "isle of anglesey" => Ok(Self::IsleOfAnglesey), + "isle of wight" => Ok(Self::IsleOfWight), + "isles of scilly" => Ok(Self::IslesOfScilly), + "islington" => Ok(Self::Islington), + "kensington and chelsea" => Ok(Self::KensingtonAndChelsea), + "kent" => Ok(Self::Kent), + "kingston upon hull" => Ok(Self::KingstonUponHull), + "kingston upon thames" => Ok(Self::KingstonUponThames), + "kirklees" => Ok(Self::Kirklees), + "knowsley" => Ok(Self::Knowsley), + "lambeth" => Ok(Self::Lambeth), + "lancashire" => Ok(Self::Lancashire), + "leeds" => Ok(Self::Leeds), + "leicester" => Ok(Self::Leicester), + "leicestershire" => Ok(Self::Leicestershire), + "lewisham" => Ok(Self::Lewisham), + "lincolnshire" => Ok(Self::Lincolnshire), + "lisburn and castlereagh" => Ok(Self::LisburnAndCastlereagh), + "liverpool" => Ok(Self::Liverpool), + "london city of" => Ok(Self::LondonCityOf), + "luton" => Ok(Self::Luton), + "manchester" => Ok(Self::Manchester), + "medway" => Ok(Self::Medway), + "merthyr tydfil" => Ok(Self::MerthyrTydfil), + "merton" => Ok(Self::Merton), + "mid and east antrim" => Ok(Self::MidAndEastAntrim), + "mid ulster" => Ok(Self::MidUlster), + "middlesbrough" => Ok(Self::Middlesbrough), + "midlothian" => Ok(Self::Midlothian), + "milton keynes" => Ok(Self::MiltonKeynes), + "monmouthshire" => Ok(Self::Monmouthshire), + "moray" => Ok(Self::Moray), + "neath port talbot" => Ok(Self::NeathPortTalbot), + "newcastle upon tyne" => Ok(Self::NewcastleUponTyne), + "newham" => Ok(Self::Newham), + "newport" => Ok(Self::Newport), + "newry mourne and down" => Ok(Self::NewryMourneAndDown), + "norfolk" => Ok(Self::Norfolk), + "north ayrshire" => Ok(Self::NorthAyrshire), + "north east lincolnshire" => Ok(Self::NorthEastLincolnshire), + "north lanarkshire" => Ok(Self::NorthLanarkshire), + "north lincolnshire" => Ok(Self::NorthLincolnshire), + "north somerset" => Ok(Self::NorthSomerset), + "north tyneside" => Ok(Self::NorthTyneside), + "north yorkshire" => Ok(Self::NorthYorkshire), + "northamptonshire" => Ok(Self::Northamptonshire), + "northumberland" => Ok(Self::Northumberland), + "nottingham" => Ok(Self::Nottingham), + "nottinghamshire" => Ok(Self::Nottinghamshire), + "oldham" => Ok(Self::Oldham), + "orkney islands" => Ok(Self::OrkneyIslands), + "oxfordshire" => Ok(Self::Oxfordshire), + "pembrokeshire" => Ok(Self::Pembrokeshire), + "perth and kinross" => Ok(Self::PerthAndKinross), + "peterborough" => Ok(Self::Peterborough), + "plymouth" => Ok(Self::Plymouth), + "portsmouth" => Ok(Self::Portsmouth), + "powys" => Ok(Self::Powys), + "reading" => Ok(Self::Reading), + "redbridge" => Ok(Self::Redbridge), + "redcar and cleveland" => Ok(Self::RedcarAndCleveland), + "renfrewshire" => Ok(Self::Renfrewshire), + "rhondda cynon taff" => Ok(Self::RhonddaCynonTaff), + "richmond upon thames" => Ok(Self::RichmondUponThames), + "rochdale" => Ok(Self::Rochdale), + "rotherham" => Ok(Self::Rotherham), + "rutland" => Ok(Self::Rutland), + "salford" => Ok(Self::Salford), + "sandwell" => Ok(Self::Sandwell), + "scottish borders" => Ok(Self::ScottishBorders), + "sefton" => Ok(Self::Sefton), + "sheffield" => Ok(Self::Sheffield), + "shetland islands" => Ok(Self::ShetlandIslands), + "shropshire" => Ok(Self::Shropshire), + "slough" => Ok(Self::Slough), + "solihull" => Ok(Self::Solihull), + "somerset" => Ok(Self::Somerset), + "south ayrshire" => Ok(Self::SouthAyrshire), + "south gloucestershire" => Ok(Self::SouthGloucestershire), + "south lanarkshire" => Ok(Self::SouthLanarkshire), + "south tyneside" => Ok(Self::SouthTyneside), + "southampton" => Ok(Self::Southampton), + "southend on sea" => Ok(Self::SouthendOnSea), + "southwark" => Ok(Self::Southwark), + "st helens" => Ok(Self::StHelens), + "staffordshire" => Ok(Self::Staffordshire), + "stirling" => Ok(Self::Stirling), + "stockport" => Ok(Self::Stockport), + "stockton on tees" => Ok(Self::StocktonOnTees), + "stoke on trent" => Ok(Self::StokeOnTrent), + "suffolk" => Ok(Self::Suffolk), + "sunderland" => Ok(Self::Sunderland), + "surrey" => Ok(Self::Surrey), + "sutton" => Ok(Self::Sutton), + "swansea" => Ok(Self::Swansea), + "swindon" => Ok(Self::Swindon), + "tameside" => Ok(Self::Tameside), + "telford and wrekin" => Ok(Self::TelfordAndWrekin), + "thurrock" => Ok(Self::Thurrock), + "torbay" => Ok(Self::Torbay), + "torfaen" => Ok(Self::Torfaen), + "tower hamlets" => Ok(Self::TowerHamlets), + "trafford" => Ok(Self::Trafford), + "vale of glamorgan" => Ok(Self::ValeOfGlamorgan), + "wakefield" => Ok(Self::Wakefield), + "walsall" => Ok(Self::Walsall), + "waltham forest" => Ok(Self::WalthamForest), + "wandsworth" => Ok(Self::Wandsworth), + "warrington" => Ok(Self::Warrington), + "warwickshire" => Ok(Self::Warwickshire), + "west berkshire" => Ok(Self::WestBerkshire), + "west dunbartonshire" => Ok(Self::WestDunbartonshire), + "west lothian" => Ok(Self::WestLothian), + "west sussex" => Ok(Self::WestSussex), + "westminster" => Ok(Self::Westminster), + "wigan" => Ok(Self::Wigan), + "wiltshire" => Ok(Self::Wiltshire), + "windsor and maidenhead" => Ok(Self::WindsorAndMaidenhead), + "wirral" => Ok(Self::Wirral), + "wokingham" => Ok(Self::Wokingham), + "wolverhampton" => Ok(Self::Wolverhampton), + "worcestershire" => Ok(Self::Worcestershire), + "wrexham" => Ok(Self::Wrexham), + "york" => Ok(Self::York), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + pub trait ForeignTryFrom: Sized { type Error; diff --git a/crates/hyperswitch_domain_models/src/mandates.rs b/crates/hyperswitch_domain_models/src/mandates.rs index 53104cc692f..6ab7a590890 100644 --- a/crates/hyperswitch_domain_models/src/mandates.rs +++ b/crates/hyperswitch_domain_models/src/mandates.rs @@ -60,7 +60,7 @@ pub struct MandateAmountData { pub struct MandateData { /// A way to update the mandate's payment method details pub update_mandate_id: Option, - /// A concent from the customer to store the payment method + /// A consent from the customer to store the payment method pub customer_acceptance: Option, /// A way to select the type of mandate used pub mandate_type: Option, diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index e681c76afb5..6edd2b7c65d 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -7036,7 +7036,10 @@ pub async fn payment_external_authentication( let billing_address = helpers::create_or_find_address_for_payment_by_request( &state, None, - payment_intent.billing_address_id.as_deref(), + payment_attempt + .payment_method_billing_address_id + .as_deref() + .or(payment_intent.billing_address_id.as_deref()), merchant_id, payment_intent.customer_id.as_ref(), &key_store, From 3607b30c26cc24341bf88f8ce9968e094fb7a60a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Sat, 15 Feb 2025 16:11:46 +0000 Subject: [PATCH 105/133] chore(version): 2025.02.15.0 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 78cce8ecebf..d514121ec75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,32 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.15.0 + +### Features + +- **connector:** [Datatrans] add mandate flow ([#7245](https://github.com/juspay/hyperswitch/pull/7245)) ([`e2043de`](https://github.com/juspay/hyperswitch/commit/e2043dee224bac63b4288e53475176f0941c4abb)) +- **core:** + - Add card_discovery filter to payment list and payments Response ([#7230](https://github.com/juspay/hyperswitch/pull/7230)) ([`3c7cb9e`](https://github.com/juspay/hyperswitch/commit/3c7cb9e59dc28bf79cf83793ae168491cfed717f)) + - Introduce accounts schema for accounts related tables ([#7113](https://github.com/juspay/hyperswitch/pull/7113)) ([`0ba4ccf`](https://github.com/juspay/hyperswitch/commit/0ba4ccfc8b38a918a56eab66715005b4c448172b)) +- **payment_methods_v2:** Add support for network tokenization ([#7145](https://github.com/juspay/hyperswitch/pull/7145)) ([`0b972e3`](https://github.com/juspay/hyperswitch/commit/0b972e38abd08380b75165dfd755087769f35a62)) +- **router:** Add v2 endpoint retrieve payment aggregate based on merchant profile ([#7196](https://github.com/juspay/hyperswitch/pull/7196)) ([`c17eb01`](https://github.com/juspay/hyperswitch/commit/c17eb01e35749343b3bf4fdda51782ea962ee57a)) +- **utils:** Add iso representation for each state for european countries ([#7273](https://github.com/juspay/hyperswitch/pull/7273)) ([`c337be6`](https://github.com/juspay/hyperswitch/commit/c337be66f9ca8b3f0a2c0a510298d4f48f09f588)) + +### Bug Fixes + +- **cypress:** Resolve cypress issue for NMI connector ([#7267](https://github.com/juspay/hyperswitch/pull/7267)) ([`0d5c6fa`](https://github.com/juspay/hyperswitch/commit/0d5c6faae06c9e6e793a271c121a43818fb3e53f)) + +### Refactors + +- **payments:** Add platform merchant account checks for payment intent ([#7204](https://github.com/juspay/hyperswitch/pull/7204)) ([`12ef8ee`](https://github.com/juspay/hyperswitch/commit/12ef8ee0fc63829429697c42b98f4c773f12cade)) +- **payments_v2:** Create customer at connector end and populate connector customer ID ([#7246](https://github.com/juspay/hyperswitch/pull/7246)) ([`17f9e6e`](https://github.com/juspay/hyperswitch/commit/17f9e6ee9e99366fa0236a3f4266483d1d8dfa22)) +- **router:** Add revenue_recovery_metadata to payment intent in diesel and api model for v2 flow ([#7176](https://github.com/juspay/hyperswitch/pull/7176)) ([`2ee22cd`](https://github.com/juspay/hyperswitch/commit/2ee22cdf8aced4881c1aab70cd10797a4deb57ed)) + +**Full Changelog:** [`2025.02.14.0...2025.02.15.0`](https://github.com/juspay/hyperswitch/compare/2025.02.14.0...2025.02.15.0) + +- - - + ## 2025.02.14.0 ### Features From c868ff38e0234fa83f1615e751af12cb7d32a3d9 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Mon, 17 Feb 2025 15:10:11 +0530 Subject: [PATCH 106/133] feat(coingate): Add Crypto Pay Flow (#7247) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 2 + api-reference/openapi_spec.json | 2 + config/config.example.toml | 2 +- config/deployments/integration_test.toml | 2 +- config/deployments/production.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- crates/common_enums/src/connector_enums.rs | 5 +- crates/connector_configs/src/connector.rs | 2 + .../connector_configs/toml/development.toml | 6 + crates/connector_configs/toml/production.toml | 8 + crates/connector_configs/toml/sandbox.toml | 6 + .../src/connectors/coingate.rs | 294 +++++------------- .../src/connectors/coingate/transformers.rs | 202 ++++-------- crates/router/src/core/admin.rs | 4 + crates/router/src/types/api.rs | 4 +- crates/router/src/types/transformers.rs | 2 +- loadtest/config/development.toml | 2 +- 19 files changed, 178 insertions(+), 373 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index cedd4abcca6..61ad3bf9762 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6967,6 +6967,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "ctp_mastercard", "cybersource", @@ -19316,6 +19317,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index e9b50395320..1919e9744f1 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9533,6 +9533,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "ctp_mastercard", "cybersource", @@ -24352,6 +24353,7 @@ "cashtocode", "checkout", "coinbase", + "coingate", "cryptopay", "cybersource", "datatrans", diff --git a/config/config.example.toml b/config/config.example.toml index 17b0d5b8c7b..3457d330c9a 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -196,7 +196,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 952cdff80e6..079134e6d03 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -42,7 +42,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index ac5a48007e1..872845ceb02 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -46,7 +46,7 @@ cashtocode.base_url = "https://cluster14.api.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api.coingate.com/v2" +coingate.base_url = "https://api.coingate.com" cryptopay.base_url = "https://business.cryptopay.me/" cybersource.base_url = "https://api.cybersource.com/" datatrans.base_url = "https://api.datatrans.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 42ec46a6cad..a92deafb20a 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -46,7 +46,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" checkout.base_url = "https://api.sandbox.checkout.com/" chargebee.base_url = "https://$.chargebee.com/api/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/development.toml b/config/development.toml index 153c8a996de..13ed82c81f6 100644 --- a/config/development.toml +++ b/config/development.toml @@ -266,7 +266,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 48581fbea06..0031c37f022 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -128,7 +128,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index ce7335ddeae..8a42bc274cb 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -68,7 +68,7 @@ pub enum RoutableConnectors { // Chargebee, Checkout, Coinbase, - // Coingate, + Coingate, Cryptopay, Cybersource, Datatrans, @@ -206,6 +206,7 @@ pub enum Connector { // Chargebee, Checkout, Coinbase, + Coingate, Cryptopay, CtpMastercard, Cybersource, @@ -359,6 +360,7 @@ impl Connector { | Self::Cashtocode // | Self::Chargebee | Self::Coinbase + |Self::Coingate | Self::Cryptopay | Self::Deutschebank | Self::Digitalvirgo @@ -547,6 +549,7 @@ impl From for Connector { RoutableConnectors::Zsl => Self::Zsl, RoutableConnectors::Xendit => Self::Xendit, RoutableConnectors::Inespay => Self::Inespay, + RoutableConnectors::Coingate => Self::Coingate, } } } diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index c24beba0a47..f0cea8d1496 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -173,6 +173,7 @@ pub struct ConnectorConfig { pub cashtocode: Option, pub checkout: Option, pub coinbase: Option, + pub coingate: Option, pub cryptopay: Option, pub cybersource: Option, #[cfg(feature = "payouts")] @@ -338,6 +339,7 @@ impl ConnectorConfig { Connector::Cashtocode => Ok(connector_data.cashtocode), Connector::Checkout => Ok(connector_data.checkout), Connector::Coinbase => Ok(connector_data.coinbase), + Connector::Coingate => Ok(connector_data.coingate), Connector::Cryptopay => Ok(connector_data.cryptopay), Connector::Cybersource => Ok(connector_data.cybersource), Connector::Iatapay => Ok(connector_data.iatapay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 72272b425d6..c4897b2e788 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1159,6 +1159,12 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + [cryptopay] [[cryptopay.crypto]] payment_method_type = "crypto_currency" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 13f0409ebf1..9251edf9003 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -996,6 +996,14 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" + +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + + [cybersource] [[cybersource.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 70895d48c8d..c13349325db 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1157,6 +1157,12 @@ api_key="API Key" [coinbase.connector_webhook_details] merchant_secret="Source verification key" +[coingate] +[[coingate.crypto]] + payment_method_type = "crypto_currency" +[coingate.connector_auth.HeaderKey] +api_key="API Key" + [cryptopay] [[cryptopay.crypto]] payment_method_type = "crypto_currency" diff --git a/crates/hyperswitch_connectors/src/connectors/coingate.rs b/crates/hyperswitch_connectors/src/connectors/coingate.rs index e3bece084b3..81bb9116950 100644 --- a/crates/hyperswitch_connectors/src/connectors/coingate.rs +++ b/crates/hyperswitch_connectors/src/connectors/coingate.rs @@ -1,10 +1,11 @@ pub mod transformers; +use common_enums::{CaptureMethod, PaymentMethod, PaymentMethodType}; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -31,25 +32,26 @@ use hyperswitch_interfaces::{ ConnectorValidation, }, configs::Connectors, + consts::NO_ERROR_CODE, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{Mask, PeekInterface}; use transformers as coingate; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Coingate { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Coingate { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -70,7 +72,6 @@ impl api::PaymentToken for Coingate {} impl ConnectorIntegration for Coingate { - // Not Implemented (R) } impl ConnectorCommonExt for Coingate @@ -99,13 +100,10 @@ impl ConnectorCommon for Coingate { fn get_currency_unit(&self) -> api::CurrencyUnit { api::CurrencyUnit::Base - // TODO! Check connector documentation, on which unit they are processing the currency. - // If the connector accepts amount in lower unit ( i.e cents for USD) then return api::CurrencyUnit::Minor, - // if connector accepts amount in base unit (i.e dollars for USD) then return api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { - "application/json" + "application/x-www-form-urlencoded" } fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { @@ -120,7 +118,7 @@ impl ConnectorCommon for Coingate { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + format!("Bearer {}", auth.api_key.peek()).into_masked(), )]) } @@ -139,19 +137,15 @@ impl ConnectorCommon for Coingate { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, + code: NO_ERROR_CODE.to_string(), message: response.message, - reason: response.reason, + reason: Some(response.reason), attempt_status: None, connector_transaction_id: None, }) } } -impl ConnectorValidation for Coingate { - //TODO: implement functions when support enabled -} - impl ConnectorIntegration for Coingate { //TODO: implement sessions flow } @@ -179,9 +173,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}{}", self.base_url(connectors), "/api/v2/orders")) } fn get_request_body( @@ -197,7 +191,7 @@ impl ConnectorIntegration for Coi fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/{}{}", + self.base_url(connectors), + "api/v2/orders/", + connector_id + )) } fn build_request( @@ -315,232 +319,78 @@ impl ConnectorIntegration for Coi } impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( - &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( + fn build_request( &self, _req: &PaymentsCaptureRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "Capture".to_string(), + connector: "Coingate".to_string(), + } + .into()) } +} +impl ConnectorIntegration for Coingate { fn build_request( &self, - req: &PaymentsCaptureRouterData, - connectors: &Connectors, + _req: &RouterData, + _connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Post) - .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::PaymentsCaptureType::get_headers( - self, req, connectors, - )?) - .set_body(types::PaymentsCaptureType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &PaymentsCaptureRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: coingate::CoingatePaymentsResponse = res - .response - .parse_struct("Coingate PaymentsCaptureResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(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) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Void".to_string(), + connector: "Coingate".to_string(), + } + .into()) } } -impl ConnectorIntegration for Coingate {} - impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &RefundsRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundsRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn get_request_body( - &self, - req: &RefundsRouterData, - _connectors: &Connectors, - ) -> CustomResult { - let refund_amount = utils::convert_amount( - self.amount_converter, - req.request.minor_refund_amount, - req.request.currency, - )?; - - let connector_router_data = coingate::CoingateRouterData::from((refund_amount, req)); - let connector_req = coingate::CoingateRefundRequest::try_from(&connector_router_data)?; - Ok(RequestContent::Json(Box::new(connector_req))) - } - - fn build_request( - &self, - req: &RefundsRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - let request = RequestBuilder::new() - .method(Method::Post) - .url(&types::RefundExecuteType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundExecuteType::get_headers( - self, req, connectors, - )?) - .set_body(types::RefundExecuteType::get_request_body( - self, req, connectors, - )?) - .build(); - Ok(Some(request)) - } - - fn handle_response( - &self, - data: &RefundsRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult, errors::ConnectorError> { - let response: coingate::RefundResponse = res - .response - .parse_struct("coingate RefundResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(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) + Err(errors::ConnectorError::FlowNotSupported { + flow: "RefundSync".to_string(), + connector: "Coingate".to_string(), + } + .into()) } } impl ConnectorIntegration for Coingate { - fn get_headers( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, - ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) - } - - fn get_content_type(&self) -> &'static str { - self.common_get_content_type() - } - - fn get_url( + fn build_request( &self, _req: &RefundSyncRouterData, _connectors: &Connectors, - ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) - } - - fn build_request( - &self, - req: &RefundSyncRouterData, - connectors: &Connectors, ) -> CustomResult, errors::ConnectorError> { - Ok(Some( - RequestBuilder::new() - .method(Method::Get) - .url(&types::RefundSyncType::get_url(self, req, connectors)?) - .attach_default_headers() - .headers(types::RefundSyncType::get_headers(self, req, connectors)?) - .set_body(types::RefundSyncType::get_request_body( - self, req, connectors, - )?) - .build(), - )) - } - - fn handle_response( - &self, - data: &RefundSyncRouterData, - event_builder: Option<&mut ConnectorEvent>, - res: Response, - ) -> CustomResult { - let response: coingate::RefundResponse = res - .response - .parse_struct("coingate RefundSyncResponse") - .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; - event_builder.map(|i| i.set_response_body(&response)); - router_env::logger::info!(connector_response=?response); - RouterData::try_from(ResponseRouterData { - response, - data: data.clone(), - http_code: res.status_code, - }) + Err(errors::ConnectorError::FlowNotSupported { + flow: "Refund".to_string(), + connector: "Coingate".to_string(), + } + .into()) } +} - fn get_error_response( - &self, - res: Response, - event_builder: Option<&mut ConnectorEvent>, - ) -> CustomResult { - self.build_error_response(res, event_builder) +impl ConnectorValidation for Coingate { + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + CaptureMethod::Automatic | CaptureMethod::SequentialAutomatic => Ok(()), + CaptureMethod::ManualMultiple | CaptureMethod::Scheduled | CaptureMethod::Manual => { + Err(utils::construct_not_supported_error_report( + capture_method, + self.id(), + )) + } + } } } diff --git a/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs index 831f30453ed..77128580c29 100644 --- a/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/coingate/transformers.rs @@ -1,31 +1,27 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use std::collections::HashMap; + +use common_enums::{enums, Currency}; +use common_utils::{request::Method, types::StringMajorUnit}; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + router_response_types::{PaymentsResponseData, RedirectForm}, + types::PaymentsAuthorizeRouterData, }; use hyperswitch_interfaces::errors; use masking::Secret; use serde::{Deserialize, Serialize}; -use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, -}; +use crate::{types::ResponseRouterData, utils}; -//TODO: Fill the struct with respective fields pub struct CoingateRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for CoingateRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(StringMajorUnit, T)> for CoingateRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +29,15 @@ impl From<(StringMinorUnit, T)> for CoingateRouterData { } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, PartialEq)] pub struct CoingatePaymentsRequest { - amount: StringMinorUnit, - card: CoingateCard, -} - -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct CoingateCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + price_amount: StringMajorUnit, + price_currency: Currency, + receive_currency: String, + callback_url: Option, + success_url: Option, + cancel_url: Option, + title: String, } impl TryFrom<&CoingateRouterData<&PaymentsAuthorizeRouterData>> for CoingatePaymentsRequest { @@ -54,27 +45,23 @@ impl TryFrom<&CoingateRouterData<&PaymentsAuthorizeRouterData>> for CoingatePaym fn try_from( item: &CoingateRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = CoingateCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.amount.clone(), - card, - }) - } - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), - } + Ok(match item.router_data.request.payment_method_data { + PaymentMethodData::Crypto(_) => Ok(Self { + price_amount: item.amount.clone(), + price_currency: item.router_data.request.currency, + receive_currency: "DO_NOT_CONVERT".to_string(), + callback_url: item.router_data.request.router_return_url.clone(), + success_url: item.router_data.request.router_return_url.clone(), + cancel_url: item.router_data.request.router_return_url.clone(), + title: item.router_data.connector_request_reference_id.clone(), + }), + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Coingate"), + )), + }?) } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct CoingateAuthType { pub(super) api_key: Secret, } @@ -90,32 +77,40 @@ impl TryFrom<&ConnectorAuthType> for CoingateAuthType { } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "lowercase")] pub enum CoingatePaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + New, + Pending, + Confirming, + Paid, + Invalid, + Expired, + Canceled, } impl From for common_enums::AttemptStatus { fn from(item: CoingatePaymentStatus) -> Self { match item { - CoingatePaymentStatus::Succeeded => Self::Charged, - CoingatePaymentStatus::Failed => Self::Failure, - CoingatePaymentStatus::Processing => Self::Authorizing, + CoingatePaymentStatus::Paid => Self::Charged, + CoingatePaymentStatus::Canceled + | CoingatePaymentStatus::Expired + | CoingatePaymentStatus::Invalid => Self::Failure, + CoingatePaymentStatus::Confirming | CoingatePaymentStatus::New => { + Self::AuthenticationPending + } + CoingatePaymentStatus::Pending => Self::Pending, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] pub struct CoingatePaymentsResponse { status: CoingatePaymentStatus, - id: String, + id: i64, + payment_url: String, + order_id: String, } impl TryFrom> @@ -126,14 +121,18 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: enums::AttemptStatus::from(item.response.status.clone()), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), - redirection_data: Box::new(None), + resource_id: ResponseId::ConnectorTransactionId(item.response.id.to_string()), + redirection_data: Box::new(Some(RedirectForm::Form { + endpoint: item.response.payment_url.clone(), + method: Method::Get, + form_fields: HashMap::new(), + })), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.order_id.clone()), incremental_authorization_allowed: None, charges: None, }), @@ -141,88 +140,9 @@ impl TryFrom TryFrom<&CoingateRouterData<&RefundsRouterData>> for CoingateRefundRequest { - type Error = error_stack::Report; - fn try_from(item: &CoingateRouterData<&RefundsRouterData>) -> Result { - Ok(Self { - amount: item.amount.to_owned(), - }) - } -} - -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} - -impl From for enums::RefundStatus { - fn from(item: RefundStatus) -> Self { - match item { - RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, - RefundStatus::Processing => Self::Pending, - //TODO: Review mapping - } - } -} - -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] -pub struct RefundResponse { - id: String, - status: RefundStatus, -} - -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } -} - -impl TryFrom> for RefundsRouterData { - type Error = error_stack::Report; - fn try_from( - item: RefundsResponseRouterData, - ) -> Result { - Ok(Self { - response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), - }), - ..item.data - }) - } -} - -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] pub struct CoingateErrorResponse { pub status_code: u16, - pub code: String, pub message: String, - pub reason: Option, + pub reason: String, } diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 958dbf74382..cdca5883f4e 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1317,6 +1317,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { coinbase::transformers::CoinbaseConnectorMeta::try_from(self.connector_meta_data)?; Ok(()) } + api_enums::Connector::Coingate => { + coingate::transformers::CoingateAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Cryptopay => { cryptopay::transformers::CryptopayAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 11f2a1902d0..1a702cb835b 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -368,7 +368,9 @@ impl ConnectorData { enums::Connector::Coinbase => { Ok(ConnectorEnum::Old(Box::new(&connector::Coinbase))) } - // enums::Connector::Coingate => Ok(ConnectorEnum::Old(Box::new(connector::Coingate))), + enums::Connector::Coingate => { + Ok(ConnectorEnum::Old(Box::new(connector::Coingate::new()))) + } enums::Connector::Cryptopay => { Ok(ConnectorEnum::Old(Box::new(connector::Cryptopay::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 99ed67ff836..f8f8830fda7 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -228,7 +228,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::Chargebee => Self::Chargebee, api_enums::Connector::Checkout => Self::Checkout, api_enums::Connector::Coinbase => Self::Coinbase, - // api_enums::Connector::Coingate => Self::Coingate, + api_enums::Connector::Coingate => Self::Coingate, api_enums::Connector::Cryptopay => Self::Cryptopay, api_enums::Connector::CtpMastercard => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index ed2ea37307d..6766d7cb7e6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -94,7 +94,7 @@ cashtocode.base_url = "https://cluster05.api-test.cashtocode.com" chargebee.base_url = "https://$.chargebee.com/api/" checkout.base_url = "https://api.sandbox.checkout.com/" coinbase.base_url = "https://api.commerce.coinbase.com" -coingate.base_url = "https://api-sandbox.coingate.com/v2" +coingate.base_url = "https://api-sandbox.coingate.com" cryptopay.base_url = "https://business-sandbox.cryptopay.me" cybersource.base_url = "https://apitest.cybersource.com/" datatrans.base_url = "https://api.sandbox.datatrans.com/" From b97370d59fd167af9c24f4470f4668ce2ee76a89 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Mon, 17 Feb 2025 18:22:32 +0530 Subject: [PATCH 107/133] refactor(utils): use to_state_code of hyperswitch_connectors in router (#7278) --- .typos.toml | 4 + crates/common_enums/src/enums.rs | 585 ++++++++++++++++++ crates/hyperswitch_connectors/src/utils.rs | 478 +++++++++++++- .../connector/netcetera/netcetera_types.rs | 7 +- .../connector/threedsecureio/transformers.rs | 3 +- crates/router/src/connector/utils.rs | 27 - 6 files changed, 1062 insertions(+), 42 deletions(-) diff --git a/.typos.toml b/.typos.toml index 75d1290eb46..1955bd608cb 100644 --- a/.typos.toml +++ b/.typos.toml @@ -59,6 +59,10 @@ OltCounty = "OltCounty" # Is a state in Romania olt = "olt" # Is iso representation of a state in Romania Vas = "Vas" # Is iso representation of a state in Hungary vas = "vas" # Is iso representation of a state in Hungary +WHT = "WHT" # Is iso representation of a state in Belgium +CantonOfEschSurAlzette = "CantonOfEschSurAlzette" # Is a state in Luxembourg +GorenjaVasPoljane = "GorenjaVasPoljane" # Is a state in Slovenia +sur = "sur" # Is iso representation of a state in Luxembourg [default.extend-words] aci = "aci" # Name of a connector diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index b8efe58b4e2..c3421185e79 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -5036,6 +5036,591 @@ pub enum UnitedKingdomStatesAbbreviation { York, } +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum BelgiumStatesAbbreviation { + #[strum(serialize = "VAN")] + Antwerp, + #[strum(serialize = "BRU")] + BrusselsCapitalRegion, + #[strum(serialize = "VOV")] + EastFlanders, + #[strum(serialize = "VLG")] + Flanders, + #[strum(serialize = "VBR")] + FlemishBrabant, + #[strum(serialize = "WHT")] + Hainaut, + #[strum(serialize = "VLI")] + Limburg, + #[strum(serialize = "WLG")] + Liege, + #[strum(serialize = "WLX")] + Luxembourg, + #[strum(serialize = "WNA")] + Namur, + #[strum(serialize = "WAL")] + Wallonia, + #[strum(serialize = "WBR")] + WalloonBrabant, + #[strum(serialize = "VWV")] + WestFlanders, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum LuxembourgStatesAbbreviation { + #[strum(serialize = "CA")] + CantonOfCapellen, + #[strum(serialize = "CL")] + CantonOfClervaux, + #[strum(serialize = "DI")] + CantonOfDiekirch, + #[strum(serialize = "EC")] + CantonOfEchternach, + #[strum(serialize = "ES")] + CantonOfEschSurAlzette, + #[strum(serialize = "GR")] + CantonOfGrevenmacher, + #[strum(serialize = "LU")] + CantonOfLuxembourg, + #[strum(serialize = "ME")] + CantonOfMersch, + #[strum(serialize = "RD")] + CantonOfRedange, + #[strum(serialize = "RM")] + CantonOfRemich, + #[strum(serialize = "VD")] + CantonOfVianden, + #[strum(serialize = "WI")] + CantonOfWiltz, + #[strum(serialize = "D")] + DiekirchDistrict, + #[strum(serialize = "G")] + GrevenmacherDistrict, + #[strum(serialize = "L")] + LuxembourgDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum RussiaStatesAbbreviation { + #[strum(serialize = "ALT")] + AltaiKrai, + #[strum(serialize = "AL")] + AltaiRepublic, + #[strum(serialize = "AMU")] + AmurOblast, + #[strum(serialize = "ARK")] + Arkhangelsk, + #[strum(serialize = "AST")] + AstrakhanOblast, + #[strum(serialize = "BEL")] + BelgorodOblast, + #[strum(serialize = "BRY")] + BryanskOblast, + #[strum(serialize = "CE")] + ChechenRepublic, + #[strum(serialize = "CHE")] + ChelyabinskOblast, + #[strum(serialize = "CHU")] + ChukotkaAutonomousOkrug, + #[strum(serialize = "CU")] + ChuvashRepublic, + #[strum(serialize = "IRK")] + Irkutsk, + #[strum(serialize = "IVA")] + IvanovoOblast, + #[strum(serialize = "YEV")] + JewishAutonomousOblast, + #[strum(serialize = "KB")] + KabardinoBalkarRepublic, + #[strum(serialize = "KGD")] + Kaliningrad, + #[strum(serialize = "KLU")] + KalugaOblast, + #[strum(serialize = "KAM")] + KamchatkaKrai, + #[strum(serialize = "KC")] + KarachayCherkessRepublic, + #[strum(serialize = "KEM")] + KemerovoOblast, + #[strum(serialize = "KHA")] + KhabarovskKrai, + #[strum(serialize = "KHM")] + KhantyMansiAutonomousOkrug, + #[strum(serialize = "KIR")] + KirovOblast, + #[strum(serialize = "KO")] + KomiRepublic, + #[strum(serialize = "KOS")] + KostromaOblast, + #[strum(serialize = "KDA")] + KrasnodarKrai, + #[strum(serialize = "KYA")] + KrasnoyarskKrai, + #[strum(serialize = "KGN")] + KurganOblast, + #[strum(serialize = "KRS")] + KurskOblast, + #[strum(serialize = "LEN")] + LeningradOblast, + #[strum(serialize = "LIP")] + LipetskOblast, + #[strum(serialize = "MAG")] + MagadanOblast, + #[strum(serialize = "ME")] + MariElRepublic, + #[strum(serialize = "MOW")] + Moscow, + #[strum(serialize = "MOS")] + MoscowOblast, + #[strum(serialize = "MUR")] + MurmanskOblast, + #[strum(serialize = "NEN")] + NenetsAutonomousOkrug, + #[strum(serialize = "NIZ")] + NizhnyNovgorodOblast, + #[strum(serialize = "NGR")] + NovgorodOblast, + #[strum(serialize = "NVS")] + Novosibirsk, + #[strum(serialize = "OMS")] + OmskOblast, + #[strum(serialize = "ORE")] + OrenburgOblast, + #[strum(serialize = "ORL")] + OryolOblast, + #[strum(serialize = "PNZ")] + PenzaOblast, + #[strum(serialize = "PER")] + PermKrai, + #[strum(serialize = "PRI")] + PrimorskyKrai, + #[strum(serialize = "PSK")] + PskovOblast, + #[strum(serialize = "AD")] + RepublicOfAdygea, + #[strum(serialize = "BA")] + RepublicOfBashkortostan, + #[strum(serialize = "BU")] + RepublicOfBuryatia, + #[strum(serialize = "DA")] + RepublicOfDagestan, + #[strum(serialize = "IN")] + RepublicOfIngushetia, + #[strum(serialize = "KL")] + RepublicOfKalmykia, + #[strum(serialize = "KR")] + RepublicOfKarelia, + #[strum(serialize = "KK")] + RepublicOfKhakassia, + #[strum(serialize = "MO")] + RepublicOfMordovia, + #[strum(serialize = "SE")] + RepublicOfNorthOssetiaAlania, + #[strum(serialize = "TA")] + RepublicOfTatarstan, + #[strum(serialize = "ROS")] + RostovOblast, + #[strum(serialize = "RYA")] + RyazanOblast, + #[strum(serialize = "SPE")] + SaintPetersburg, + #[strum(serialize = "SA")] + SakhaRepublic, + #[strum(serialize = "SAK")] + Sakhalin, + #[strum(serialize = "SAM")] + SamaraOblast, + #[strum(serialize = "SAR")] + SaratovOblast, + #[strum(serialize = "UA-40")] + Sevastopol, + #[strum(serialize = "SMO")] + SmolenskOblast, + #[strum(serialize = "STA")] + StavropolKrai, + #[strum(serialize = "SVE")] + Sverdlovsk, + #[strum(serialize = "TAM")] + TambovOblast, + #[strum(serialize = "TOM")] + TomskOblast, + #[strum(serialize = "TUL")] + TulaOblast, + #[strum(serialize = "TY")] + TuvaRepublic, + #[strum(serialize = "TVE")] + TverOblast, + #[strum(serialize = "TYU")] + TyumenOblast, + #[strum(serialize = "UD")] + UdmurtRepublic, + #[strum(serialize = "ULY")] + UlyanovskOblast, + #[strum(serialize = "VLA")] + VladimirOblast, + #[strum(serialize = "VLG")] + VologdaOblast, + #[strum(serialize = "VOR")] + VoronezhOblast, + #[strum(serialize = "YAN")] + YamaloNenetsAutonomousOkrug, + #[strum(serialize = "YAR")] + YaroslavlOblast, + #[strum(serialize = "ZAB")] + ZabaykalskyKrai, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SanMarinoStatesAbbreviation { + #[strum(serialize = "01")] + Acquaviva, + #[strum(serialize = "06")] + BorgoMaggiore, + #[strum(serialize = "02")] + Chiesanuova, + #[strum(serialize = "03")] + Domagnano, + #[strum(serialize = "04")] + Faetano, + #[strum(serialize = "05")] + Fiorentino, + #[strum(serialize = "08")] + Montegiardino, + #[strum(serialize = "07")] + SanMarino, + #[strum(serialize = "09")] + Serravalle, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SerbiaStatesAbbreviation { + #[strum(serialize = "00")] + Belgrade, + + #[strum(serialize = "01")] + BorDistrict, + + #[strum(serialize = "02")] + BraničevoDistrict, + + #[strum(serialize = "03")] + CentralBanatDistrict, + + #[strum(serialize = "04")] + JablanicaDistrict, + + #[strum(serialize = "05")] + KolubaraDistrict, + + #[strum(serialize = "06")] + MačvaDistrict, + + #[strum(serialize = "07")] + MoravicaDistrict, + + #[strum(serialize = "08")] + NišavaDistrict, + + #[strum(serialize = "09")] + NorthBanatDistrict, + + #[strum(serialize = "10")] + NorthBačkaDistrict, + + #[strum(serialize = "11")] + PirotDistrict, + + #[strum(serialize = "12")] + PodunavljeDistrict, + + #[strum(serialize = "13")] + PomoravljeDistrict, + + #[strum(serialize = "14")] + PčinjaDistrict, + + #[strum(serialize = "15")] + RasinaDistrict, + + #[strum(serialize = "16")] + RaškaDistrict, + + #[strum(serialize = "17")] + SouthBanatDistrict, + + #[strum(serialize = "18")] + SouthBačkaDistrict, + + #[strum(serialize = "19")] + SremDistrict, + + #[strum(serialize = "20")] + ToplicaDistrict, + + #[strum(serialize = "21")] + Vojvodina, + + #[strum(serialize = "22")] + WestBačkaDistrict, + + #[strum(serialize = "23")] + ZaječarDistrict, + + #[strum(serialize = "24")] + ZlatiborDistrict, + + #[strum(serialize = "25")] + ŠumadijaDistrict, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SlovakiaStatesAbbreviation { + #[strum(serialize = "BC")] + BanskaBystricaRegion, + #[strum(serialize = "BL")] + BratislavaRegion, + #[strum(serialize = "KI")] + KosiceRegion, + #[strum(serialize = "NI")] + NitraRegion, + #[strum(serialize = "PV")] + PresovRegion, + #[strum(serialize = "TC")] + TrencinRegion, + #[strum(serialize = "TA")] + TrnavaRegion, + #[strum(serialize = "ZI")] + ZilinaRegion, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SloveniaStatesAbbreviation { + #[strum(serialize = "001")] + Ajdovščina, + #[strum(serialize = "213")] + Ankaran, + #[strum(serialize = "002")] + Beltinci, + #[strum(serialize = "148")] + Benedikt, + #[strum(serialize = "149")] + BistricaObSotli, + #[strum(serialize = "003")] + Bled, + #[strum(serialize = "150")] + Bloke, + #[strum(serialize = "004")] + Bohinj, + #[strum(serialize = "005")] + Borovnica, + #[strum(serialize = "006")] + Bovec, + #[strum(serialize = "151")] + Braslovče, + #[strum(serialize = "007")] + Brda, + #[strum(serialize = "008")] + Brezovica, + #[strum(serialize = "009")] + Brežice, + #[strum(serialize = "152")] + Cankova, + #[strum(serialize = "012")] + CerkljeNaGorenjskem, + #[strum(serialize = "013")] + Cerknica, + #[strum(serialize = "014")] + Cerkno, + #[strum(serialize = "153")] + Cerkvenjak, + #[strum(serialize = "011")] + CityMunicipalityOfCelje, + #[strum(serialize = "085")] + CityMunicipalityOfNovoMesto, + #[strum(serialize = "018")] + Destrnik, + #[strum(serialize = "019")] + Divača, + #[strum(serialize = "154")] + Dobje, + #[strum(serialize = "020")] + Dobrepolje, + #[strum(serialize = "155")] + Dobrna, + #[strum(serialize = "021")] + DobrovaPolhovGradec, + #[strum(serialize = "156")] + Dobrovnik, + #[strum(serialize = "022")] + DolPriLjubljani, + #[strum(serialize = "157")] + DolenjskeToplice, + #[strum(serialize = "023")] + Domžale, + #[strum(serialize = "024")] + Dornava, + #[strum(serialize = "025")] + Dravograd, + #[strum(serialize = "026")] + Duplek, + #[strum(serialize = "027")] + GorenjaVasPoljane, + #[strum(serialize = "028")] + Gorišnica, + #[strum(serialize = "207")] + Gorje, + #[strum(serialize = "029")] + GornjaRadgona, + #[strum(serialize = "030")] + GornjiGrad, + #[strum(serialize = "031")] + GornjiPetrovci, + #[strum(serialize = "158")] + Grad, + #[strum(serialize = "032")] + Grosuplje, + #[strum(serialize = "159")] + Hajdina, + #[strum(serialize = "161")] + Hodoš, + #[strum(serialize = "162")] + Horjul, + #[strum(serialize = "160")] + HočeSlivnica, + #[strum(serialize = "034")] + Hrastnik, + #[strum(serialize = "035")] + HrpeljeKozina, + #[strum(serialize = "036")] + Idrija, + #[strum(serialize = "037")] + Ig, + #[strum(serialize = "039")] + IvančnaGorica, + #[strum(serialize = "040")] + Izola, + #[strum(serialize = "041")] + Jesenice, + #[strum(serialize = "163")] + Jezersko, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum SwedenStatesAbbreviation { + #[strum(serialize = "K")] + Blekinge, + #[strum(serialize = "W")] + DalarnaCounty, + #[strum(serialize = "I")] + GotlandCounty, + #[strum(serialize = "X")] + GävleborgCounty, + #[strum(serialize = "N")] + HallandCounty, + #[strum(serialize = "F")] + JönköpingCounty, + #[strum(serialize = "H")] + KalmarCounty, + #[strum(serialize = "G")] + KronobergCounty, + #[strum(serialize = "BD")] + NorrbottenCounty, + #[strum(serialize = "M")] + SkåneCounty, + #[strum(serialize = "AB")] + StockholmCounty, + #[strum(serialize = "D")] + SödermanlandCounty, + #[strum(serialize = "C")] + UppsalaCounty, + #[strum(serialize = "S")] + VärmlandCounty, + #[strum(serialize = "AC")] + VästerbottenCounty, + #[strum(serialize = "Y")] + VästernorrlandCounty, + #[strum(serialize = "U")] + VästmanlandCounty, + #[strum(serialize = "O")] + VästraGötalandCounty, + #[strum(serialize = "T")] + ÖrebroCounty, + #[strum(serialize = "E")] + ÖstergötlandCounty, +} + +#[derive( + Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, +)] +pub enum UkraineStatesAbbreviation { + #[strum(serialize = "43")] + AutonomousRepublicOfCrimea, + #[strum(serialize = "71")] + CherkasyOblast, + #[strum(serialize = "74")] + ChernihivOblast, + #[strum(serialize = "77")] + ChernivtsiOblast, + #[strum(serialize = "12")] + DnipropetrovskOblast, + #[strum(serialize = "14")] + DonetskOblast, + #[strum(serialize = "26")] + IvanoFrankivskOblast, + #[strum(serialize = "63")] + KharkivOblast, + #[strum(serialize = "65")] + KhersonOblast, + #[strum(serialize = "68")] + KhmelnytskyOblast, + #[strum(serialize = "30")] + Kiev, + #[strum(serialize = "35")] + KirovohradOblast, + #[strum(serialize = "32")] + KyivOblast, + #[strum(serialize = "09")] + LuhanskOblast, + #[strum(serialize = "46")] + LvivOblast, + #[strum(serialize = "48")] + MykolaivOblast, + #[strum(serialize = "51")] + OdessaOblast, + #[strum(serialize = "56")] + RivneOblast, + #[strum(serialize = "59")] + SumyOblast, + #[strum(serialize = "61")] + TernopilOblast, + #[strum(serialize = "05")] + VinnytsiaOblast, + #[strum(serialize = "07")] + VolynOblast, + #[strum(serialize = "21")] + ZakarpattiaOblast, + #[strum(serialize = "23")] + ZaporizhzhyaOblast, + #[strum(serialize = "18")] + ZhytomyrOblast, +} + #[derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 4ed91f5ffdb..efb8eb86f34 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -8,18 +8,21 @@ use common_enums::{ enums, enums::{ AlbaniaStatesAbbreviation, AndorraStatesAbbreviation, AttemptStatus, - AustriaStatesAbbreviation, BelarusStatesAbbreviation, + AustriaStatesAbbreviation, BelarusStatesAbbreviation, BelgiumStatesAbbreviation, BosniaAndHerzegovinaStatesAbbreviation, BulgariaStatesAbbreviation, CanadaStatesAbbreviation, CroatiaStatesAbbreviation, CzechRepublicStatesAbbreviation, DenmarkStatesAbbreviation, FinlandStatesAbbreviation, FranceStatesAbbreviation, FutureUsage, GermanyStatesAbbreviation, GreeceStatesAbbreviation, HungaryStatesAbbreviation, IcelandStatesAbbreviation, IrelandStatesAbbreviation, ItalyStatesAbbreviation, LatviaStatesAbbreviation, LiechtensteinStatesAbbreviation, - LithuaniaStatesAbbreviation, MaltaStatesAbbreviation, MoldovaStatesAbbreviation, - MonacoStatesAbbreviation, MontenegroStatesAbbreviation, NetherlandsStatesAbbreviation, - NorthMacedoniaStatesAbbreviation, NorwayStatesAbbreviation, PolandStatesAbbreviation, - PortugalStatesAbbreviation, RomaniaStatesAbbreviation, SpainStatesAbbreviation, - SwitzerlandStatesAbbreviation, UnitedKingdomStatesAbbreviation, UsStatesAbbreviation, + LithuaniaStatesAbbreviation, LuxembourgStatesAbbreviation, MaltaStatesAbbreviation, + MoldovaStatesAbbreviation, MonacoStatesAbbreviation, MontenegroStatesAbbreviation, + NetherlandsStatesAbbreviation, NorthMacedoniaStatesAbbreviation, NorwayStatesAbbreviation, + PolandStatesAbbreviation, PortugalStatesAbbreviation, RomaniaStatesAbbreviation, + RussiaStatesAbbreviation, SanMarinoStatesAbbreviation, SerbiaStatesAbbreviation, + SlovakiaStatesAbbreviation, SloveniaStatesAbbreviation, SpainStatesAbbreviation, + SwedenStatesAbbreviation, SwitzerlandStatesAbbreviation, UkraineStatesAbbreviation, + UnitedKingdomStatesAbbreviation, UsStatesAbbreviation, }, }; use common_utils::{ @@ -3508,11 +3511,11 @@ impl ForeignTryFrom for MaltaStatesAbbreviation { StringExt::::parse_enum(value.to_uppercase().clone(), "MaltaStatesAbbreviation"); match state_abbreviation_check { - Ok(municipality) => Ok(municipality), + Ok(state_abbreviation) => Ok(state_abbreviation), Err(_) => { let binding = value.as_str().to_lowercase(); - let municipality = binding.as_str(); - match municipality { + let state = binding.as_str(); + match state { "attard" => Ok(Self::Attard), "balzan" => Ok(Self::Balzan), "birgu" => Ok(Self::Birgu), @@ -4404,6 +4407,463 @@ impl ForeignTryFrom for UnitedKingdomStatesAbbreviation { } } +impl ForeignTryFrom for BelgiumStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "BelgiumStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "antwerp" => Ok(Self::Antwerp), + "brussels capital region" => Ok(Self::BrusselsCapitalRegion), + "east flanders" => Ok(Self::EastFlanders), + "flanders" => Ok(Self::Flanders), + "flemish brabant" => Ok(Self::FlemishBrabant), + "hainaut" => Ok(Self::Hainaut), + "limburg" => Ok(Self::Limburg), + "liege" => Ok(Self::Liege), + "luxembourg" => Ok(Self::Luxembourg), + "namur" => Ok(Self::Namur), + "wallonia" => Ok(Self::Wallonia), + "walloon brabant" => Ok(Self::WalloonBrabant), + "west flanders" => Ok(Self::WestFlanders), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for LuxembourgStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "LuxembourgStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "canton of capellen" => Ok(Self::CantonOfCapellen), + "canton of clervaux" => Ok(Self::CantonOfClervaux), + "canton of diekirch" => Ok(Self::CantonOfDiekirch), + "canton of echternach" => Ok(Self::CantonOfEchternach), + "canton of esch sur alzette" => Ok(Self::CantonOfEschSurAlzette), + "canton of grevenmacher" => Ok(Self::CantonOfGrevenmacher), + "canton of luxembourg" => Ok(Self::CantonOfLuxembourg), + "canton of mersch" => Ok(Self::CantonOfMersch), + "canton of redange" => Ok(Self::CantonOfRedange), + "canton of remich" => Ok(Self::CantonOfRemich), + "canton of vianden" => Ok(Self::CantonOfVianden), + "canton of wiltz" => Ok(Self::CantonOfWiltz), + "diekirch district" => Ok(Self::DiekirchDistrict), + "grevenmacher district" => Ok(Self::GrevenmacherDistrict), + "luxembourg district" => Ok(Self::LuxembourgDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for RussiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "RussiaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "altai krai" => Ok(Self::AltaiKrai), + "altai republic" => Ok(Self::AltaiRepublic), + "amur oblast" => Ok(Self::AmurOblast), + "arkhangelsk" => Ok(Self::Arkhangelsk), + "astrakhan oblast" => Ok(Self::AstrakhanOblast), + "belgorod oblast" => Ok(Self::BelgorodOblast), + "bryansk oblast" => Ok(Self::BryanskOblast), + "chechen republic" => Ok(Self::ChechenRepublic), + "chelyabinsk oblast" => Ok(Self::ChelyabinskOblast), + "chukotka autonomous okrug" => Ok(Self::ChukotkaAutonomousOkrug), + "chuvash republic" => Ok(Self::ChuvashRepublic), + "irkutsk" => Ok(Self::Irkutsk), + "ivanovo oblast" => Ok(Self::IvanovoOblast), + "jewish autonomous oblast" => Ok(Self::JewishAutonomousOblast), + "kabardino-balkar republic" => Ok(Self::KabardinoBalkarRepublic), + "kaliningrad" => Ok(Self::Kaliningrad), + "kaluga oblast" => Ok(Self::KalugaOblast), + "kamchatka krai" => Ok(Self::KamchatkaKrai), + "karachay-cherkess republic" => Ok(Self::KarachayCherkessRepublic), + "kemerovo oblast" => Ok(Self::KemerovoOblast), + "khabarovsk krai" => Ok(Self::KhabarovskKrai), + "khanty-mansi autonomous okrug" => Ok(Self::KhantyMansiAutonomousOkrug), + "kirov oblast" => Ok(Self::KirovOblast), + "komi republic" => Ok(Self::KomiRepublic), + "kostroma oblast" => Ok(Self::KostromaOblast), + "krasnodar krai" => Ok(Self::KrasnodarKrai), + "krasnoyarsk krai" => Ok(Self::KrasnoyarskKrai), + "kurgan oblast" => Ok(Self::KurganOblast), + "kursk oblast" => Ok(Self::KurskOblast), + "leningrad oblast" => Ok(Self::LeningradOblast), + "lipetsk oblast" => Ok(Self::LipetskOblast), + "magadan oblast" => Ok(Self::MagadanOblast), + "mari el republic" => Ok(Self::MariElRepublic), + "moscow" => Ok(Self::Moscow), + "moscow oblast" => Ok(Self::MoscowOblast), + "murmansk oblast" => Ok(Self::MurmanskOblast), + "nenets autonomous okrug" => Ok(Self::NenetsAutonomousOkrug), + "nizhny novgorod oblast" => Ok(Self::NizhnyNovgorodOblast), + "novgorod oblast" => Ok(Self::NovgorodOblast), + "novosibirsk" => Ok(Self::Novosibirsk), + "omsk oblast" => Ok(Self::OmskOblast), + "orenburg oblast" => Ok(Self::OrenburgOblast), + "oryol oblast" => Ok(Self::OryolOblast), + "penza oblast" => Ok(Self::PenzaOblast), + "perm krai" => Ok(Self::PermKrai), + "primorsky krai" => Ok(Self::PrimorskyKrai), + "pskov oblast" => Ok(Self::PskovOblast), + "republic of adygea" => Ok(Self::RepublicOfAdygea), + "republic of bashkortostan" => Ok(Self::RepublicOfBashkortostan), + "republic of buryatia" => Ok(Self::RepublicOfBuryatia), + "republic of dagestan" => Ok(Self::RepublicOfDagestan), + "republic of ingushetia" => Ok(Self::RepublicOfIngushetia), + "republic of kalmykia" => Ok(Self::RepublicOfKalmykia), + "republic of karelia" => Ok(Self::RepublicOfKarelia), + "republic of khakassia" => Ok(Self::RepublicOfKhakassia), + "republic of mordovia" => Ok(Self::RepublicOfMordovia), + "republic of north ossetia-alania" => Ok(Self::RepublicOfNorthOssetiaAlania), + "republic of tatarstan" => Ok(Self::RepublicOfTatarstan), + "rostov oblast" => Ok(Self::RostovOblast), + "ryazan oblast" => Ok(Self::RyazanOblast), + "saint petersburg" => Ok(Self::SaintPetersburg), + "sakha republic" => Ok(Self::SakhaRepublic), + "sakhalin" => Ok(Self::Sakhalin), + "samara oblast" => Ok(Self::SamaraOblast), + "saratov oblast" => Ok(Self::SaratovOblast), + "sevastopol" => Ok(Self::Sevastopol), + "smolensk oblast" => Ok(Self::SmolenskOblast), + "stavropol krai" => Ok(Self::StavropolKrai), + "sverdlovsk" => Ok(Self::Sverdlovsk), + "tambov oblast" => Ok(Self::TambovOblast), + "tomsk oblast" => Ok(Self::TomskOblast), + "tula oblast" => Ok(Self::TulaOblast), + "tuva republic" => Ok(Self::TuvaRepublic), + "tver oblast" => Ok(Self::TverOblast), + "tyumen oblast" => Ok(Self::TyumenOblast), + "udmurt republic" => Ok(Self::UdmurtRepublic), + "ulyanovsk oblast" => Ok(Self::UlyanovskOblast), + "vladimir oblast" => Ok(Self::VladimirOblast), + "vologda oblast" => Ok(Self::VologdaOblast), + "voronezh oblast" => Ok(Self::VoronezhOblast), + "yamalo-nenets autonomous okrug" => Ok(Self::YamaloNenetsAutonomousOkrug), + "yaroslavl oblast" => Ok(Self::YaroslavlOblast), + "zabaykalsky krai" => Ok(Self::ZabaykalskyKrai), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SanMarinoStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "SanMarinoStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "acquaviva" => Ok(Self::Acquaviva), + "borgo maggiore" => Ok(Self::BorgoMaggiore), + "chiesanuova" => Ok(Self::Chiesanuova), + "domagnano" => Ok(Self::Domagnano), + "faetano" => Ok(Self::Faetano), + "fiorentino" => Ok(Self::Fiorentino), + "montegiardino" => Ok(Self::Montegiardino), + "san marino" => Ok(Self::SanMarino), + "serravalle" => Ok(Self::Serravalle), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SerbiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "SerbiaStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "belgrade" => Ok(Self::Belgrade), + "bor district" => Ok(Self::BorDistrict), + "braničevo district" => Ok(Self::BraničevoDistrict), + "central banat district" => Ok(Self::CentralBanatDistrict), + "jablanica district" => Ok(Self::JablanicaDistrict), + "kolubara district" => Ok(Self::KolubaraDistrict), + "mačva district" => Ok(Self::MačvaDistrict), + "moravica district" => Ok(Self::MoravicaDistrict), + "nišava district" => Ok(Self::NišavaDistrict), + "north banat district" => Ok(Self::NorthBanatDistrict), + "north bačka district" => Ok(Self::NorthBačkaDistrict), + "pirot district" => Ok(Self::PirotDistrict), + "podunavlje district" => Ok(Self::PodunavljeDistrict), + "pomoravlje district" => Ok(Self::PomoravljeDistrict), + "pčinja district" => Ok(Self::PčinjaDistrict), + "rasina district" => Ok(Self::RasinaDistrict), + "raška district" => Ok(Self::RaškaDistrict), + "south banat district" => Ok(Self::SouthBanatDistrict), + "south bačka district" => Ok(Self::SouthBačkaDistrict), + "srem district" => Ok(Self::SremDistrict), + "toplica district" => Ok(Self::ToplicaDistrict), + "vojvodina" => Ok(Self::Vojvodina), + "west bačka district" => Ok(Self::WestBačkaDistrict), + "zaječar district" => Ok(Self::ZaječarDistrict), + "zlatibor district" => Ok(Self::ZlatiborDistrict), + "šumadija district" => Ok(Self::ŠumadijaDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SlovakiaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "SlovakiaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "banska bystrica region" => Ok(Self::BanskaBystricaRegion), + "bratislava region" => Ok(Self::BratislavaRegion), + "kosice region" => Ok(Self::KosiceRegion), + "nitra region" => Ok(Self::NitraRegion), + "presov region" => Ok(Self::PresovRegion), + "trencin region" => Ok(Self::TrencinRegion), + "trnava region" => Ok(Self::TrnavaRegion), + "zilina region" => Ok(Self::ZilinaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SwedenStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = + StringExt::::parse_enum(value.to_uppercase().clone(), "SwedenStatesAbbreviation"); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "blekinge" => Ok(Self::Blekinge), + "dalarna county" => Ok(Self::DalarnaCounty), + "gotland county" => Ok(Self::GotlandCounty), + "gävleborg county" => Ok(Self::GävleborgCounty), + "halland county" => Ok(Self::HallandCounty), + "jönköping county" => Ok(Self::JönköpingCounty), + "kalmar county" => Ok(Self::KalmarCounty), + "kronoberg county" => Ok(Self::KronobergCounty), + "norrbotten county" => Ok(Self::NorrbottenCounty), + "skåne county" => Ok(Self::SkåneCounty), + "stockholm county" => Ok(Self::StockholmCounty), + "södermanland county" => Ok(Self::SödermanlandCounty), + "uppsala county" => Ok(Self::UppsalaCounty), + "värmland county" => Ok(Self::VärmlandCounty), + "västerbotten county" => Ok(Self::VästerbottenCounty), + "västernorrland county" => Ok(Self::VästernorrlandCounty), + "västmanland county" => Ok(Self::VästmanlandCounty), + "västra götaland county" => Ok(Self::VästraGötalandCounty), + "örebro county" => Ok(Self::ÖrebroCounty), + "östergötland county" => Ok(Self::ÖstergötlandCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for SloveniaStatesAbbreviation { + type Error = error_stack::Report; + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "SloveniaStatesAbbreviation", + ); + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + match state { + "ajdovščina" => Ok(Self::Ajdovščina), + "ankaran" => Ok(Self::Ankaran), + "beltinci" => Ok(Self::Beltinci), + "benedikt" => Ok(Self::Benedikt), + "bistrica ob sotli" => Ok(Self::BistricaObSotli), + "bled" => Ok(Self::Bled), + "bloke" => Ok(Self::Bloke), + "bohinj" => Ok(Self::Bohinj), + "borovnica" => Ok(Self::Borovnica), + "bovec" => Ok(Self::Bovec), + "braslovče" => Ok(Self::Braslovče), + "brda" => Ok(Self::Brda), + "brezovica" => Ok(Self::Brezovica), + "brežice" => Ok(Self::Brežice), + "cankova" => Ok(Self::Cankova), + "cerklje na gorenjskem" => Ok(Self::CerkljeNaGorenjskem), + "cerknica" => Ok(Self::Cerknica), + "cerkno" => Ok(Self::Cerkno), + "cerkvenjak" => Ok(Self::Cerkvenjak), + "city municipality of celje" => Ok(Self::CityMunicipalityOfCelje), + "city municipality of novo mesto" => Ok(Self::CityMunicipalityOfNovoMesto), + "destrnik" => Ok(Self::Destrnik), + "divača" => Ok(Self::Divača), + "dobje" => Ok(Self::Dobje), + "dobrepolje" => Ok(Self::Dobrepolje), + "dobrna" => Ok(Self::Dobrna), + "dobrova-polhov gradec" => Ok(Self::DobrovaPolhovGradec), + "dobrovnik" => Ok(Self::Dobrovnik), + "dol pri ljubljani" => Ok(Self::DolPriLjubljani), + "dolenjske toplice" => Ok(Self::DolenjskeToplice), + "domžale" => Ok(Self::Domžale), + "dornava" => Ok(Self::Dornava), + "dravograd" => Ok(Self::Dravograd), + "duplek" => Ok(Self::Duplek), + "gorenja vas-poljane" => Ok(Self::GorenjaVasPoljane), + "gorišnica" => Ok(Self::Gorišnica), + "gorje" => Ok(Self::Gorje), + "gornja radgona" => Ok(Self::GornjaRadgona), + "gornji grad" => Ok(Self::GornjiGrad), + "gornji petrovci" => Ok(Self::GornjiPetrovci), + "grad" => Ok(Self::Grad), + "grosuplje" => Ok(Self::Grosuplje), + "hajdina" => Ok(Self::Hajdina), + "hodoš" => Ok(Self::Hodoš), + "horjul" => Ok(Self::Horjul), + "hoče-slivnica" => Ok(Self::HočeSlivnica), + "hrastnik" => Ok(Self::Hrastnik), + "hrpelje-kozina" => Ok(Self::HrpeljeKozina), + "idrija" => Ok(Self::Idrija), + "ig" => Ok(Self::Ig), + "ivančna gorica" => Ok(Self::IvančnaGorica), + "izola" => Ok(Self::Izola), + "jesenice" => Ok(Self::Jesenice), + "jezersko" => Ok(Self::Jezersko), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + +impl ForeignTryFrom for UkraineStatesAbbreviation { + type Error = error_stack::Report; + + fn foreign_try_from(value: String) -> Result { + let state_abbreviation_check = StringExt::::parse_enum( + value.to_uppercase().clone(), + "UkraineStatesAbbreviation", + ); + + match state_abbreviation_check { + Ok(state_abbreviation) => Ok(state_abbreviation), + Err(_) => { + let binding = value.as_str().to_lowercase(); + let state = binding.as_str(); + + match state { + "autonomous republic of crimea" => Ok(Self::AutonomousRepublicOfCrimea), + "cherkasy oblast" => Ok(Self::CherkasyOblast), + "chernihiv oblast" => Ok(Self::ChernihivOblast), + "chernivtsi oblast" => Ok(Self::ChernivtsiOblast), + "dnipropetrovsk oblast" => Ok(Self::DnipropetrovskOblast), + "donetsk oblast" => Ok(Self::DonetskOblast), + "ivano-frankivsk oblast" => Ok(Self::IvanoFrankivskOblast), + "kharkiv oblast" => Ok(Self::KharkivOblast), + "kherson oblast" => Ok(Self::KhersonOblast), + "khmelnytsky oblast" => Ok(Self::KhmelnytskyOblast), + "kiev" => Ok(Self::Kiev), + "kirovohrad oblast" => Ok(Self::KirovohradOblast), + "kyiv oblast" => Ok(Self::KyivOblast), + "luhansk oblast" => Ok(Self::LuhanskOblast), + "lviv oblast" => Ok(Self::LvivOblast), + "mykolaiv oblast" => Ok(Self::MykolaivOblast), + "odessa oblast" => Ok(Self::OdessaOblast), + "rivne oblast" => Ok(Self::RivneOblast), + "sumy oblast" => Ok(Self::SumyOblast), + "ternopil oblast" => Ok(Self::TernopilOblast), + "vinnytsia oblast" => Ok(Self::VinnytsiaOblast), + "volyn oblast" => Ok(Self::VolynOblast), + "zakarpattia oblast" => Ok(Self::ZakarpattiaOblast), + "zaporizhzhya oblast" => Ok(Self::ZaporizhzhyaOblast), + "zhytomyr oblast" => Ok(Self::ZhytomyrOblast), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + } + } + } + } +} + pub trait ForeignTryFrom: Sized { type Error; diff --git a/crates/router/src/connector/netcetera/netcetera_types.rs b/crates/router/src/connector/netcetera/netcetera_types.rs index 36daa62e558..8f4a596131c 100644 --- a/crates/router/src/connector/netcetera/netcetera_types.rs +++ b/crates/router/src/connector/netcetera/netcetera_types.rs @@ -1,15 +1,12 @@ use std::collections::HashMap; use common_utils::pii::Email; +use hyperswitch_connectors::utils::AddressDetailsData; use masking::ExposeInterface; use serde::{Deserialize, Serialize}; use unidecode::unidecode; -use crate::{ - connector::utils::{AddressDetailsData, PhoneDetailsData}, - errors, - types::api::MessageCategory, -}; +use crate::{connector::utils::PhoneDetailsData, errors, types::api::MessageCategory}; #[derive(Debug, Deserialize, Serialize, Clone)] #[serde(untagged)] diff --git a/crates/router/src/connector/threedsecureio/transformers.rs b/crates/router/src/connector/threedsecureio/transformers.rs index 140cef0fbce..c66a0aa0b67 100644 --- a/crates/router/src/connector/threedsecureio/transformers.rs +++ b/crates/router/src/connector/threedsecureio/transformers.rs @@ -4,6 +4,7 @@ use api_models::payments::{DeviceChannel, ThreeDsCompletionIndicator}; use base64::Engine; use common_utils::date_time; use error_stack::ResultExt; +use hyperswitch_connectors::utils::AddressDetailsData; use iso_currency::Currency; use isocountry; use masking::{ExposeInterface, Secret}; @@ -11,7 +12,7 @@ use serde::{Deserialize, Serialize}; use serde_json::{json, to_string}; use crate::{ - connector::utils::{get_card_details, to_connector_meta, AddressDetailsData, CardData}, + connector::utils::{get_card_details, to_connector_meta, CardData}, consts::{BASE64_ENGINE, NO_ERROR_MESSAGE}, core::errors, types::{ diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 0aca263b5c7..2d7e3cc6d52 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1857,8 +1857,6 @@ pub trait AddressDetailsData { fn get_zip(&self) -> Result<&Secret, Error>; fn get_country(&self) -> Result<&api_models::enums::CountryAlpha2, Error>; fn get_combined_address_line(&self) -> Result, Error>; - fn to_state_code(&self) -> Result, Error>; - fn to_state_code_as_optional(&self) -> Result>, Error>; fn get_optional_line2(&self) -> Option>; fn get_optional_country(&self) -> Option; } @@ -1931,31 +1929,6 @@ impl AddressDetailsData for hyperswitch_domain_models::address::AddressDetails { self.get_line2()?.peek() ))) } - fn to_state_code(&self) -> Result, Error> { - let country = self.get_country()?; - let state = self.get_state()?; - match country { - api_models::enums::CountryAlpha2::US => Ok(Secret::new( - UsStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), - )), - api_models::enums::CountryAlpha2::CA => Ok(Secret::new( - CanadaStatesAbbreviation::foreign_try_from(state.peek().to_string())?.to_string(), - )), - _ => Ok(state.clone()), - } - } - fn to_state_code_as_optional(&self) -> Result>, Error> { - self.state - .as_ref() - .map(|state| { - if state.peek().len() == 2 { - Ok(state.to_owned()) - } else { - self.to_state_code() - } - }) - .transpose() - } fn get_optional_line2(&self) -> Option> { self.line2.clone() From 90fbb62ecd418b85804c6ae824843237ff740302 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Feb 2025 00:26:58 +0000 Subject: [PATCH 108/133] chore(version): 2025.02.18.0 --- CHANGELOG.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d514121ec75..704fda9678b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.18.0 + +### Features + +- **coingate:** Add Crypto Pay Flow ([#7247](https://github.com/juspay/hyperswitch/pull/7247)) ([`c868ff3`](https://github.com/juspay/hyperswitch/commit/c868ff38e0234fa83f1615e751af12cb7d32a3d9)) + +### Refactors + +- **utils:** Use to_state_code of hyperswitch_connectors in router ([#7278](https://github.com/juspay/hyperswitch/pull/7278)) ([`b97370d`](https://github.com/juspay/hyperswitch/commit/b97370d59fd167af9c24f4470f4668ce2ee76a89)) + +**Full Changelog:** [`2025.02.15.0...2025.02.18.0`](https://github.com/juspay/hyperswitch/compare/2025.02.15.0...2025.02.18.0) + +- - - + ## 2025.02.15.0 ### Features From 72080c67c7927b53d5ca013983f379e9b027c51f Mon Sep 17 00:00:00 2001 From: Debarati Ghatak <88573135+cookieg13@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:36:45 +0530 Subject: [PATCH 109/133] feat(payments): [Payment links] Add configs for payment link (#7288) --- api-reference-v2/openapi_spec.json | 20 ++++++ api-reference/openapi_spec.json | 20 ++++++ crates/api_models/src/admin.rs | 8 +++ crates/api_models/src/payments.rs | 4 ++ crates/diesel_models/src/business_profile.rs | 2 + crates/diesel_models/src/payment_intent.rs | 4 ++ crates/hyperswitch_domain_models/src/lib.rs | 6 ++ crates/router/src/core/payment_link.rs | 66 +++++++++---------- .../payment_link_initiate/payment_link.css | 1 - .../payment_link_initiate/payment_link.js | 1 + .../payment_link_initiator.js | 1 + .../secure_payment_link_initiator.js | 1 + .../router/src/core/payments/transformers.rs | 4 ++ crates/router/src/macros.rs | 33 ++++++++++ crates/router/src/routes/payments.rs | 4 +- crates/router/src/types/transformers.rs | 4 ++ 16 files changed, 141 insertions(+), 38 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 61ad3bf9762..9c4339b7e36 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -13373,6 +13373,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -13463,6 +13473,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 1919e9744f1..57153fd2337 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -16429,6 +16429,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, @@ -16519,6 +16529,16 @@ "type": "string", "description": "Text for payment link's handle confirm button", "nullable": true + }, + "custom_message_for_card_terms": { + "type": "string", + "description": "Text for customizing message for card terms", + "nullable": true + }, + "payment_button_colour": { + "type": "string", + "description": "Custom background colour for payment link's handle confirm button", + "nullable": true } } }, diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index c1312433947..7a0ece8520d 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -2708,6 +2708,10 @@ pub struct PaymentLinkConfigRequest { pub details_layout: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] @@ -2779,6 +2783,10 @@ pub struct PaymentLinkConfig { pub branding_visibility: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } #[derive(Debug, Clone, serde::Serialize, serde::Deserialize, PartialEq, Eq)] diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index f996de726e4..bf7dd377394 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -7531,6 +7531,8 @@ pub struct PaymentLinkDetails { pub details_layout: Option, pub branding_visibility: Option, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Debug, serde::Serialize, Clone)] @@ -7541,6 +7543,8 @@ pub struct SecurePaymentLinkDetails { #[serde(flatten)] pub payment_link_details: PaymentLinkDetails, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Debug, serde::Serialize)] diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 2cbd7deb426..70f969bd2a2 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -584,6 +584,8 @@ pub struct PaymentLinkConfigRequest { pub background_image: Option, pub details_layout: Option, pub payment_button_text: Option, + pub custom_message_for_card_terms: Option, + pub payment_button_colour: Option, } #[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq)] diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 4cb48f6c36e..57233306c25 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -171,6 +171,10 @@ pub struct PaymentLinkConfigRequestForPayments { pub details_layout: Option, /// Text for payment link's handle confirm button pub payment_button_text: Option, + /// Text for customizing message for card terms + pub custom_message_for_card_terms: Option, + /// Custom background colour for payment link's handle confirm button + pub payment_button_colour: Option, } common_utils::impl_to_sql_from_sql_json!(PaymentLinkConfigRequestForPayments); diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 6c8f499388a..3a1bd933b51 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -404,6 +404,8 @@ impl ApiModelToDieselModelConvertor ) }), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } fn convert_back(self) -> api_models::admin::PaymentLinkConfigRequest { @@ -420,6 +422,8 @@ impl ApiModelToDieselModelConvertor background_image, details_layout, payment_button_text, + custom_message_for_card_terms, + payment_button_colour, } = self; api_models::admin::PaymentLinkConfigRequest { theme, @@ -440,6 +444,8 @@ impl ApiModelToDieselModelConvertor background_image: background_image .map(|background_image| background_image.convert_back()), payment_button_text, + custom_message_for_card_terms, + payment_button_colour, } } } diff --git a/crates/router/src/core/payment_link.rs b/crates/router/src/core/payment_link.rs index 477803c92a2..ba63319aabb 100644 --- a/crates/router/src/core/payment_link.rs +++ b/crates/router/src/core/payment_link.rs @@ -130,6 +130,8 @@ pub async fn form_payment_link_data( details_layout: None, branding_visibility: None, payment_button_text: None, + custom_message_for_card_terms: None, + payment_button_colour: None, } }; @@ -276,6 +278,8 @@ pub async fn form_payment_link_data( details_layout: payment_link_config.details_layout, branding_visibility: payment_link_config.branding_visibility, payment_button_text: payment_link_config.payment_button_text.clone(), + custom_message_for_card_terms: payment_link_config.custom_message_for_card_terms.clone(), + payment_button_colour: payment_link_config.payment_button_colour.clone(), }; Ok(( @@ -327,6 +331,8 @@ pub async fn initiate_secure_payment_link_flow( show_card_form_by_default: payment_link_config.show_card_form_by_default, payment_link_details: *link_details.to_owned(), payment_button_text: payment_link_config.payment_button_text, + custom_message_for_card_terms: payment_link_config.custom_message_for_card_terms, + payment_button_colour: payment_link_config.payment_button_colour, }; let js_script = format!( "window.__PAYMENT_DETAILS = {}", @@ -625,6 +631,24 @@ pub fn get_payment_link_config_based_on_priority( (hide_card_nickname_field, DEFAULT_HIDE_CARD_NICKNAME_FIELD), (show_card_form_by_default, DEFAULT_SHOW_CARD_FORM) ); + + let ( + details_layout, + background_image, + payment_button_text, + custom_message_for_card_terms, + payment_button_colour, + ) = get_payment_link_config_value!( + payment_create_link_config, + business_theme_configs, + (details_layout), + (background_image, |background_image| background_image + .foreign_into()), + (payment_button_text), + (custom_message_for_card_terms), + (payment_button_colour), + ); + let payment_link_config = PaymentLinkConfig { theme, @@ -640,41 +664,11 @@ pub fn get_payment_link_config_based_on_priority( transaction_details: payment_create_link_config.as_ref().and_then( |payment_link_config| payment_link_config.theme_config.transaction_details.clone(), ), - details_layout: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| payment_link_config.theme_config.details_layout) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| business_theme_config.details_layout) - }), - background_image: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| { - payment_link_config.theme_config.background_image.clone() - }) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| { - business_theme_config - .background_image - .as_ref() - .map(|background_image| background_image.clone().foreign_into()) - }) - }), - payment_button_text: payment_create_link_config - .as_ref() - .and_then(|payment_link_config| { - payment_link_config.theme_config.payment_button_text.clone() - }) - .or_else(|| { - business_theme_configs - .as_ref() - .and_then(|business_theme_config| { - business_theme_config.payment_button_text.clone() - }) - }), + details_layout, + background_image, + payment_button_text, + custom_message_for_card_terms, + payment_button_colour, }; Ok((payment_link_config, domain_name)) @@ -778,6 +772,8 @@ pub async fn get_payment_link_status( details_layout: None, branding_visibility: None, payment_button_text: None, + custom_message_for_card_terms: None, + payment_button_colour: None, } }; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css index b8ccdeba33a..7b347306809 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.css @@ -640,7 +640,6 @@ body { margin-top: 20px; width: 100%; height: 38px; - background-color: var(--primary-color); border: 0; border-radius: 4px; font-size: 18px; diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js index 378900ba9ae..a73067a5bce 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link.js @@ -308,6 +308,7 @@ function initializeEventListeners(paymentDetails) { if (submitButtonNode instanceof HTMLButtonElement) { submitButtonNode.style.color = contrastBWColor; + submitButtonNode.style.backgroundColor = paymentDetails.payment_button_colour || primaryColor; } if (hyperCheckoutCartImageNode instanceof HTMLDivElement) { diff --git a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js index 2d2b2c30ae2..6abe5f7d5b2 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/payment_link_initiator.js @@ -64,6 +64,7 @@ function initializeSDK() { }, showCardFormByDefault: paymentDetails.show_card_form_by_default, hideCardNicknameField: hideCardNicknameField, + customMessageForCardTerms: paymentDetails.custom_message_for_card_terms, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); diff --git a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js index 9ae64ec3133..a8a00e4de9d 100644 --- a/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js +++ b/crates/router/src/core/payment_link/payment_link_initiate/secure_payment_link_initiator.js @@ -87,6 +87,7 @@ if (!isFramed) { }, hideCardNicknameField: hideCardNicknameField, showCardFormByDefault: paymentDetails.show_card_form_by_default, + customMessageForCardTerms: paymentDetails.custom_message_for_card_terms, }; // @ts-ignore unifiedCheckout = widgets.create("payment", unifiedCheckoutOptions); diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 7302831e509..49ff8eb35da 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -4301,6 +4301,8 @@ impl ForeignFrom ) }), payment_button_text: config.payment_button_text, + custom_message_for_card_terms: config.custom_message_for_card_terms, + payment_button_colour: config.payment_button_colour, } } } @@ -4364,6 +4366,8 @@ impl ForeignFrom ) }), payment_button_text: config.payment_button_text, + custom_message_for_card_terms: config.custom_message_for_card_terms, + payment_button_colour: config.payment_button_colour, } } } diff --git a/crates/router/src/macros.rs b/crates/router/src/macros.rs index 962bdb96529..580ccb73f53 100644 --- a/crates/router/src/macros.rs +++ b/crates/router/src/macros.rs @@ -32,4 +32,37 @@ macro_rules! get_payment_link_config_value { $(get_payment_link_config_value_based_on_priority!($config, $business_config, $field, $default)),* ) }; + ($config:expr, $business_config:expr, $(($field:ident)),*) => { + ( + $( + $config + .as_ref() + .and_then(|pc_config| pc_config.theme_config.$field.clone()) + .or_else(|| { + $business_config + .as_ref() + .and_then(|business_config| business_config.$field.clone()) + }) + ),* + ) + }; + ($config:expr, $business_config:expr, $(($field:ident $(, $transform:expr)?)),* $(,)?) => { + ( + $( + $config + .as_ref() + .and_then(|pc_config| pc_config.theme_config.$field.clone()) + .or_else(|| { + $business_config + .as_ref() + .and_then(|business_config| { + let value = business_config.$field.clone(); + $(let value = value.map($transform);)? + value + }) + }) + ),* + ) + }; + } diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index dd15079efed..7fa38d6cbb2 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -2566,7 +2566,7 @@ pub async fn payments_finish_redirection( let locking_action = payload.get_locking_input(flow.clone()); - api::server_wrap( + Box::pin(api::server_wrap( flow, state, &req, @@ -2588,7 +2588,7 @@ pub async fn payments_finish_redirection( profile_id: profile_id.clone(), }, locking_action, - ) + )) .await } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index f8f8830fda7..7ac8792b507 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -2105,6 +2105,8 @@ impl ForeignFrom .background_image .map(|background_image| background_image.foreign_into()), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } } @@ -2128,6 +2130,8 @@ impl ForeignFrom .background_image .map(|background_image| background_image.foreign_into()), payment_button_text: item.payment_button_text, + custom_message_for_card_terms: item.custom_message_for_card_terms, + payment_button_colour: item.payment_button_colour, } } } From e14d6c4465bb1276a348a668051c084af72de8e3 Mon Sep 17 00:00:00 2001 From: Hrithikesh <61539176+hrithikesh026@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:42:19 +0530 Subject: [PATCH 110/133] feat(core): api ,domain and diesel model changes for extended authorization (#6607) Co-authored-by: Gnanasundari24 <118818938+Gnanasundari24@users.noreply.github.com> Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference/openapi_spec.json | 56 ++++++ crates/api_models/src/admin.rs | 13 +- crates/api_models/src/lib.rs | 9 + crates/api_models/src/payments.rs | 34 +++- crates/api_models/src/payments/trait_impls.rs | 29 +++ crates/common_utils/src/types.rs | 7 + .../src/types/primitive_wrappers.rs | 107 +++++++++++ crates/diesel_models/src/business_profile.rs | 9 +- crates/diesel_models/src/payment_attempt.rs | 18 +- crates/diesel_models/src/payment_intent.rs | 9 +- crates/diesel_models/src/schema.rs | 5 + crates/diesel_models/src/schema_v2.rs | 5 + crates/diesel_models/src/user/sample_data.rs | 11 +- .../src/business_profile.rs | 14 +- .../hyperswitch_domain_models/src/payments.rs | 3 +- .../src/payments/payment_attempt.rs | 166 ++++++++++++------ .../src/payments/payment_intent.rs | 4 + crates/router/src/core/admin.rs | 1 + crates/router/src/core/payments/helpers.rs | 6 + .../payments/operations/payment_create.rs | 4 + crates/router/src/core/payments/retry.rs | 3 + .../router/src/core/payments/transformers.rs | 4 + crates/router/src/types/api/admin.rs | 2 + .../src/types/storage/payment_attempt.rs | 9 + crates/router/src/utils/user/sample_data.rs | 4 + crates/router/tests/payments.rs | 4 + crates/router/tests/payments2.rs | 4 + .../src/mock_db/payment_attempt.rs | 3 + .../src/payments/payment_attempt.rs | 15 ++ .../down.sql | 16 ++ .../up.sql | 21 +++ 31 files changed, 535 insertions(+), 60 deletions(-) create mode 100644 crates/api_models/src/payments/trait_impls.rs create mode 100644 crates/common_utils/src/types/primitive_wrappers.rs create mode 100644 migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql create mode 100644 migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 57153fd2337..48e376791b3 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -18291,6 +18291,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -18684,6 +18690,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -19231,6 +19243,17 @@ "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", "nullable": true }, + "extended_authorization_applied": { + "type": "boolean", + "description": "flag that indicates if extended authorization is applied on this payment or not", + "nullable": true + }, + "capture_before": { + "type": "string", + "format": "date-time", + "description": "date and time after which this payment cannot be captured", + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -19909,6 +19932,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -20482,6 +20511,17 @@ "description": "You can specify up to 50 keys, with key names up to 40 characters long and values up to 500 characters long. FRM Metadata is useful for storing additional, structured information on an object related to FRM.", "nullable": true }, + "extended_authorization_applied": { + "type": "boolean", + "description": "flag that indicates if extended authorization is applied on this payment or not", + "nullable": true + }, + "capture_before": { + "type": "string", + "format": "date-time", + "description": "date and time after which this payment cannot be captured", + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -20973,6 +21013,12 @@ ], "nullable": true }, + "request_extended_authorization": { + "type": "boolean", + "description": "Optional boolean value to extent authorization period of this payment\n\ncapture method must be manual or manual_multiple", + "default": false, + "nullable": true + }, "merchant_order_reference_id": { "type": "string", "description": "Merchant's identifier for the payment/invoice. This will be sent to the connector\nif the connector provides support to accept multiple reference ids.\nIn case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference.", @@ -22978,6 +23024,11 @@ "nullable": true, "minimum": 0 }, + "always_request_extended_authorization": { + "type": "boolean", + "description": "Bool indicating if extended authentication must be requested for all payments", + "nullable": true + }, "is_click_to_pay_enabled": { "type": "boolean", "description": "Indicates if click to pay is enabled or not." @@ -23217,6 +23268,11 @@ "description": "Maximum number of auto retries allowed for a payment", "nullable": true }, + "always_request_extended_authorization": { + "type": "boolean", + "description": "Bool indicating if extended authentication must be requested for all payments", + "nullable": true + }, "is_click_to_pay_enabled": { "type": "boolean", "description": "Indicates if click to pay is enabled or not.", diff --git a/crates/api_models/src/admin.rs b/crates/api_models/src/admin.rs index 7a0ece8520d..77488262c75 100644 --- a/crates/api_models/src/admin.rs +++ b/crates/api_models/src/admin.rs @@ -8,7 +8,10 @@ use common_utils::{ id_type, link_utils, pii, }; #[cfg(feature = "v1")] -use common_utils::{crypto::OptionalEncryptableName, ext_traits::ValueExt}; +use common_utils::{ + crypto::OptionalEncryptableName, ext_traits::ValueExt, + types::AlwaysRequestExtendedAuthorization, +}; #[cfg(feature = "v2")] use masking::ExposeInterface; use masking::{PeekInterface, Secret}; @@ -1897,6 +1900,10 @@ pub struct ProfileCreate { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + /// Bool indicating if extended authentication must be requested for all payments + #[schema(value_type = Option)] + pub always_request_extended_authorization: Option, + /// Indicates if click to pay is enabled or not. #[serde(default)] pub is_click_to_pay_enabled: bool, @@ -2152,6 +2159,10 @@ pub struct ProfileResponse { /// Maximum number of auto retries allowed for a payment pub max_auto_retries_enabled: Option, + /// Bool indicating if extended authentication must be requested for all payments + #[schema(value_type = Option)] + pub always_request_extended_authorization: Option, + /// Indicates if click to pay is enabled or not. #[schema(default = false, example = false)] pub is_click_to_pay_enabled: bool, diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 60d1d11f27d..5ec80d2b539 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -41,3 +41,12 @@ pub mod verifications; pub mod verify_connector; pub mod webhook_events; pub mod webhooks; + +pub trait ValidateFieldAndGet { + fn validate_field_and_get( + &self, + request: &Request, + ) -> common_utils::errors::CustomResult + where + Self: Sized; +} diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index bf7dd377394..7b1ba943ee7 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -4,6 +4,7 @@ use std::{ num::NonZeroI64, }; pub mod additional_info; +pub mod trait_impls; use cards::CardNumber; #[cfg(feature = "v2")] use common_enums::enums::PaymentConnectorTransmission; @@ -18,7 +19,10 @@ use common_utils::{ hashing::HashedString, id_type, pii::{self, Email}, - types::{MinorUnit, StringMajorUnit}, + types::{ + ExtendedAuthorizationAppliedBool, MinorUnit, RequestExtendedAuthorizationBool, + StringMajorUnit, + }, }; use error_stack::ResultExt; use masking::{PeekInterface, Secret, WithType}; @@ -37,7 +41,7 @@ use crate::{ admin::{self, MerchantConnectorInfo}, disputes, enums as api_enums, mandates::RecurringDetails, - refunds, + refunds, ValidateFieldAndGet, }; #[derive(Clone, Copy, Debug, Eq, PartialEq)] @@ -1042,6 +1046,12 @@ pub struct PaymentsRequest { #[schema(value_type = Option)] pub split_payments: Option, + /// Optional boolean value to extent authorization period of this payment + /// + /// capture method must be manual or manual_multiple + #[schema(value_type = Option, default = false)] + pub request_extended_authorization: Option, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. @@ -1101,6 +1111,18 @@ impl PaymentsRequest { .or(self.customer.as_ref().map(|customer| &customer.id)) } + pub fn validate_and_get_request_extended_authorization( + &self, + ) -> common_utils::errors::CustomResult, ValidationError> + { + self.request_extended_authorization + .as_ref() + .map(|request_extended_authorization| { + request_extended_authorization.validate_field_and_get(self) + }) + .transpose() + } + /// Checks if the customer details are passed in both places /// If they are passed in both places, check for both the values to be equal /// Or else, return the field which has inconsistent data @@ -4842,6 +4864,14 @@ pub struct PaymentsResponse { #[schema(value_type = Option, example = r#"{ "fulfillment_method" : "deliver", "coverage_request" : "fraud" }"#)] pub frm_metadata: Option, + /// flag that indicates if extended authorization is applied on this payment or not + #[schema(value_type = Option)] + pub extended_authorization_applied: Option, + + /// date and time after which this payment cannot be captured + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub capture_before: Option, + /// Merchant's identifier for the payment/invoice. This will be sent to the connector /// if the connector provides support to accept multiple reference ids. /// In case the connector supports only one reference id, Hyperswitch's Payment ID will be sent as reference. diff --git a/crates/api_models/src/payments/trait_impls.rs b/crates/api_models/src/payments/trait_impls.rs new file mode 100644 index 00000000000..5df07a24a98 --- /dev/null +++ b/crates/api_models/src/payments/trait_impls.rs @@ -0,0 +1,29 @@ +#[cfg(feature = "v1")] +use common_enums::enums; +#[cfg(feature = "v1")] +use common_utils::errors; + +#[cfg(feature = "v1")] +use crate::payments; + +#[cfg(feature = "v1")] +impl crate::ValidateFieldAndGet + for common_utils::types::RequestExtendedAuthorizationBool +{ + fn validate_field_and_get( + &self, + request: &payments::PaymentsRequest, + ) -> errors::CustomResult + where + Self: Sized, + { + match request.capture_method{ + Some(enums::CaptureMethod::Automatic) + | Some(enums::CaptureMethod::Scheduled) + | Some(enums::CaptureMethod::SequentialAutomatic) + | None => Err(error_stack::report!(errors::ValidationError::InvalidValue { message: "request_extended_authorization must be sent only if capture method is manual or manual_multiple".to_string() })), + Some(enums::CaptureMethod::Manual) + | Some(enums::CaptureMethod::ManualMultiple) => Ok(*self) + } + } +} diff --git a/crates/common_utils/src/types.rs b/crates/common_utils/src/types.rs index 0377a3e8183..9a105933e14 100644 --- a/crates/common_utils/src/types.rs +++ b/crates/common_utils/src/types.rs @@ -6,6 +6,9 @@ pub mod authentication; /// Enum for Theme Lineage pub mod theme; +/// types that are wrappers around primitive types +pub mod primitive_wrappers; + use std::{ borrow::Cow, fmt::Display, @@ -26,6 +29,10 @@ use diesel::{ AsExpression, FromSqlRow, Queryable, }; use error_stack::{report, ResultExt}; +pub use primitive_wrappers::bool_wrappers::{ + AlwaysRequestExtendedAuthorization, ExtendedAuthorizationAppliedBool, + RequestExtendedAuthorizationBool, +}; use rust_decimal::{ prelude::{FromPrimitive, ToPrimitive}, Decimal, diff --git a/crates/common_utils/src/types/primitive_wrappers.rs b/crates/common_utils/src/types/primitive_wrappers.rs new file mode 100644 index 00000000000..850f6a7cb5f --- /dev/null +++ b/crates/common_utils/src/types/primitive_wrappers.rs @@ -0,0 +1,107 @@ +pub(crate) mod bool_wrappers { + use serde::{Deserialize, Serialize}; + + /// Bool that represents if Extended Authorization is Applied or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct ExtendedAuthorizationAppliedBool(bool); + impl From for ExtendedAuthorizationAppliedBool { + fn from(value: bool) -> Self { + Self(value) + } + } + impl diesel::serialize::ToSql for ExtendedAuthorizationAppliedBool + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for ExtendedAuthorizationAppliedBool + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } + + /// Bool that represents if Extended Authorization is Requested or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, Serialize, Deserialize, diesel::expression::AsExpression, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct RequestExtendedAuthorizationBool(bool); + impl From for RequestExtendedAuthorizationBool { + fn from(value: bool) -> Self { + Self(value) + } + } + impl RequestExtendedAuthorizationBool { + /// returns the inner bool value + pub fn is_true(&self) -> bool { + self.0 + } + } + impl diesel::serialize::ToSql for RequestExtendedAuthorizationBool + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for RequestExtendedAuthorizationBool + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } + + /// Bool that represents if Extended Authorization is always Requested or not + #[derive( + Clone, Copy, Debug, Eq, PartialEq, diesel::expression::AsExpression, Serialize, Deserialize, + )] + #[diesel(sql_type = diesel::sql_types::Bool)] + pub struct AlwaysRequestExtendedAuthorization(bool); + impl diesel::serialize::ToSql + for AlwaysRequestExtendedAuthorization + where + DB: diesel::backend::Backend, + bool: diesel::serialize::ToSql, + { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, DB>, + ) -> diesel::serialize::Result { + self.0.to_sql(out) + } + } + impl diesel::deserialize::FromSql + for AlwaysRequestExtendedAuthorization + where + DB: diesel::backend::Backend, + bool: diesel::deserialize::FromSql, + { + fn from_sql(value: DB::RawValue<'_>) -> diesel::deserialize::Result { + bool::from_sql(value).map(Self) + } + } +} diff --git a/crates/diesel_models/src/business_profile.rs b/crates/diesel_models/src/business_profile.rs index 70f969bd2a2..92da1978458 100644 --- a/crates/diesel_models/src/business_profile.rs +++ b/crates/diesel_models/src/business_profile.rs @@ -1,7 +1,7 @@ use std::collections::{HashMap, HashSet}; use common_enums::{AuthenticationConnectors, UIWidgetFormLayout}; -use common_utils::{encryption::Encryption, pii}; +use common_utils::{encryption::Encryption, pii, types::AlwaysRequestExtendedAuthorization}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use masking::Secret; @@ -57,6 +57,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -146,6 +147,7 @@ pub struct ProfileUpdateInternal { pub is_network_tokenization_enabled: Option, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: Option, pub authentication_product_ids: Option, @@ -188,6 +190,7 @@ impl ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization, is_click_to_pay_enabled, authentication_product_ids, } = self; @@ -249,6 +252,8 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + always_request_extended_authorization: always_request_extended_authorization + .or(source.always_request_extended_authorization), is_click_to_pay_enabled: is_click_to_pay_enabled .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids @@ -307,6 +312,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: Option, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -527,6 +533,7 @@ impl ProfileUpdateInternal { .unwrap_or(source.is_network_tokenization_enabled), is_auto_retries_enabled: is_auto_retries_enabled.or(source.is_auto_retries_enabled), max_auto_retries_enabled: max_auto_retries_enabled.or(source.max_auto_retries_enabled), + always_request_extended_authorization: None, is_click_to_pay_enabled: is_click_to_pay_enabled .unwrap_or(source.is_click_to_pay_enabled), authentication_product_ids: authentication_product_ids diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 2391422ee0c..8cf6b604b92 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -1,6 +1,9 @@ use common_utils::{ id_type, pii, - types::{ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit}, + types::{ + ConnectorTransactionId, ConnectorTransactionIdTrait, ExtendedAuthorizationAppliedBool, + MinorUnit, RequestExtendedAuthorizationBool, + }, }; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; @@ -93,6 +96,9 @@ pub struct PaymentAttempt { pub id: id_type::GlobalAttemptId, pub shipping_cost: Option, pub order_tax_amount: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, pub charges: Option, } @@ -174,6 +180,9 @@ pub struct PaymentAttempt { /// INFO: This field is deprecated and replaced by processor_transaction_data pub connector_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub processor_transaction_data: Option, pub card_discovery: Option, pub charges: Option, @@ -303,6 +312,9 @@ pub struct PaymentAttemptNew { pub id: id_type::GlobalAttemptId, pub connector_token_details: Option, pub card_discovery: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub charges: Option, } @@ -376,6 +388,10 @@ pub struct PaymentAttemptNew { pub shipping_cost: Option, pub order_tax_amount: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub capture_before: Option, pub card_discovery: Option, } diff --git a/crates/diesel_models/src/payment_intent.rs b/crates/diesel_models/src/payment_intent.rs index 57233306c25..f10616ceb1b 100644 --- a/crates/diesel_models/src/payment_intent.rs +++ b/crates/diesel_models/src/payment_intent.rs @@ -1,5 +1,9 @@ use common_enums::{PaymentMethodType, RequestIncrementalAuthorization}; -use common_utils::{encryption::Encryption, pii, types::MinorUnit}; +use common_utils::{ + encryption::Encryption, + pii, + types::{MinorUnit, RequestExtendedAuthorizationBool}, +}; use diesel::{AsChangeset, Identifiable, Insertable, Queryable, Selectable}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -72,6 +76,7 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, pub payment_link_config: Option, pub id: common_utils::id_type::GlobalPaymentId, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub split_payments: Option, pub platform_merchant_id: Option, @@ -139,6 +144,7 @@ pub struct PaymentIntent { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub split_payments: Option, pub platform_merchant_id: Option, @@ -372,6 +378,7 @@ pub struct PaymentIntentNew { pub organization_id: common_utils::id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub platform_merchant_id: Option, pub split_payments: Option, diff --git a/crates/diesel_models/src/schema.rs b/crates/diesel_models/src/schema.rs index 2e8219c5ab4..cfc0f28e4d0 100644 --- a/crates/diesel_models/src/schema.rs +++ b/crates/diesel_models/src/schema.rs @@ -216,6 +216,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, } @@ -910,6 +911,9 @@ diesel::table! { #[max_length = 512] connector_transaction_data -> Nullable, connector_mandate_detail -> Nullable, + request_extended_authorization -> Nullable, + extended_authorization_applied -> Nullable, + capture_before -> Nullable, processor_transaction_data -> Nullable, card_discovery -> Nullable, charges -> Nullable, @@ -993,6 +997,7 @@ diesel::table! { organization_id -> Varchar, tax_details -> Nullable, skip_external_tax_calculation -> Nullable, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, #[max_length = 64] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index ab2195526f5..c4db38c0ee7 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -224,6 +224,7 @@ diesel::table! { is_network_tokenization_enabled -> Bool, is_auto_retries_enabled -> Nullable, max_auto_retries_enabled -> Nullable, + always_request_extended_authorization -> Nullable, is_click_to_pay_enabled -> Bool, authentication_product_ids -> Nullable, three_ds_decision_manager_config -> Nullable, @@ -877,6 +878,9 @@ diesel::table! { id -> Varchar, shipping_cost -> Nullable, order_tax_amount -> Nullable, + request_extended_authorization -> Nullable, + extended_authorization_applied -> Nullable, + capture_before -> Nullable, card_discovery -> Nullable, charges -> Nullable, } @@ -952,6 +956,7 @@ diesel::table! { payment_link_config -> Nullable, #[max_length = 64] id -> Varchar, + request_extended_authorization -> Nullable, psd2_sca_exemption_type -> Nullable, split_payments -> Nullable, #[max_length = 64] diff --git a/crates/diesel_models/src/user/sample_data.rs b/crates/diesel_models/src/user/sample_data.rs index 27085424083..d6c8d5616fa 100644 --- a/crates/diesel_models/src/user/sample_data.rs +++ b/crates/diesel_models/src/user/sample_data.rs @@ -2,7 +2,10 @@ use common_enums::{ AttemptStatus, AuthenticationType, CaptureMethod, Currency, PaymentExperience, PaymentMethod, PaymentMethodType, }; -use common_utils::types::{ConnectorTransactionId, MinorUnit}; +use common_utils::types::{ + ConnectorTransactionId, ExtendedAuthorizationAppliedBool, MinorUnit, + RequestExtendedAuthorizationBool, +}; use serde::{Deserialize, Serialize}; use time::PrimitiveDateTime; @@ -203,6 +206,9 @@ pub struct PaymentAttemptBatchNew { pub order_tax_amount: Option, pub processor_transaction_data: Option, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, } @@ -282,6 +288,9 @@ impl PaymentAttemptBatchNew { shipping_cost: self.shipping_cost, order_tax_amount: self.order_tax_amount, connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, } } diff --git a/crates/hyperswitch_domain_models/src/business_profile.rs b/crates/hyperswitch_domain_models/src/business_profile.rs index b2385efb6d2..4a6a84124a7 100644 --- a/crates/hyperswitch_domain_models/src/business_profile.rs +++ b/crates/hyperswitch_domain_models/src/business_profile.rs @@ -4,7 +4,7 @@ use common_utils::{ encryption::Encryption, errors::{CustomResult, ValidationError}, pii, type_name, - types::keymanager, + types::{keymanager, AlwaysRequestExtendedAuthorization}, }; use diesel_models::business_profile::{ AuthenticationConnectorDetails, BusinessPaymentLinkConfig, BusinessPayoutLinkConfig, @@ -58,6 +58,7 @@ pub struct Profile { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -101,6 +102,7 @@ pub struct ProfileSetter { pub is_network_tokenization_enabled: bool, pub is_auto_retries_enabled: bool, pub max_auto_retries_enabled: Option, + pub always_request_extended_authorization: Option, pub is_click_to_pay_enabled: bool, pub authentication_product_ids: Option, @@ -151,6 +153,7 @@ impl From for Profile { is_network_tokenization_enabled: value.is_network_tokenization_enabled, is_auto_retries_enabled: value.is_auto_retries_enabled, max_auto_retries_enabled: value.max_auto_retries_enabled, + always_request_extended_authorization: value.always_request_extended_authorization, is_click_to_pay_enabled: value.is_click_to_pay_enabled, authentication_product_ids: value.authentication_product_ids, } @@ -306,6 +309,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled, is_auto_retries_enabled, max_auto_retries_enabled, + always_request_extended_authorization: None, is_click_to_pay_enabled, authentication_product_ids, } @@ -347,6 +351,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -386,6 +391,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -425,6 +431,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -464,6 +471,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: None, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -503,6 +511,7 @@ impl From for ProfileUpdateInternal { is_network_tokenization_enabled: Some(is_network_tokenization_enabled), is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: None, authentication_product_ids: None, }, @@ -561,6 +570,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: Some(self.is_auto_retries_enabled), max_auto_retries_enabled: self.max_auto_retries_enabled, + always_request_extended_authorization: self.always_request_extended_authorization, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, }) @@ -631,6 +641,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled.unwrap_or(false), max_auto_retries_enabled: item.max_auto_retries_enabled, + always_request_extended_authorization: item.always_request_extended_authorization, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, }) @@ -1343,6 +1354,7 @@ impl super::behaviour::Conversion for Profile { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: None, max_auto_retries_enabled: None, + always_request_extended_authorization: None, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, three_ds_decision_manager_config: self.three_ds_decision_manager_config, diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 2457448f213..0b98885fdaf 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -11,7 +11,7 @@ use common_utils::{ encryption::Encryption, errors::CustomResult, id_type, pii, - types::{keymanager::ToEncryptable, MinorUnit}, + types::{keymanager::ToEncryptable, MinorUnit, RequestExtendedAuthorizationBool}, }; use diesel_models::payment_intent::TaxDetails; #[cfg(feature = "v2")] @@ -108,6 +108,7 @@ pub struct PaymentIntent { pub organization_id: id_type::OrganizationId, pub tax_details: Option, pub skip_external_tax_calculation: Option, + pub request_extended_authorization: Option, pub psd2_sca_exemption_type: Option, pub platform_merchant_id: Option, } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index af462443cdf..f557e7c907b 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -11,7 +11,8 @@ use common_utils::{ id_type, pii, types::{ keymanager::{self, KeyManagerState}, - ConnectorTransactionId, ConnectorTransactionIdTrait, MinorUnit, + ConnectorTransactionId, ConnectorTransactionIdTrait, ExtendedAuthorizationAppliedBool, + MinorUnit, RequestExtendedAuthorizationBool, }, }; use diesel_models::{ @@ -614,6 +615,9 @@ pub struct PaymentAttempt { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, pub charges: Option, } @@ -861,6 +865,9 @@ pub struct PaymentAttemptNew { pub profile_id: id_type::ProfileId, pub organization_id: id_type::OrganizationId, pub connector_mandate_detail: Option, + pub request_extended_authorization: Option, + pub extended_authorization_applied: Option, + pub capture_before: Option, pub card_discovery: Option, } @@ -1585,6 +1592,9 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, @@ -1671,6 +1681,9 @@ impl behaviour::Conversion for PaymentAttempt { profile_id: storage_model.profile_id, organization_id: storage_model.organization_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, charges: storage_model.charges, }) @@ -1754,6 +1767,9 @@ impl behaviour::Conversion for PaymentAttempt { order_tax_amount: self.net_amount.get_order_tax_amount(), shipping_cost: self.net_amount.get_shipping_cost(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, }) } @@ -1900,6 +1916,9 @@ impl behaviour::Conversion for PaymentAttempt { connector_payment_data, connector_token_details, card_discovery, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, charges, }) } @@ -2023,9 +2042,54 @@ impl behaviour::Conversion for PaymentAttempt { async fn construct_new(self) -> CustomResult { use common_utils::encryption::Encryption; + let Self { + payment_id, + merchant_id, + status, + error, + amount_details, + authentication_type, + created_at, + modified_at, + last_synced, + cancellation_reason, + browser_info, + payment_token, + connector_metadata, + payment_experience, + payment_method_data, + routing_result, + preprocessing_step_id, + multiple_capture_count, + connector_response_reference_id, + updated_by, + redirection_data, + encoded_data, + merchant_connector_id, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + fingerprint_id, + client_source, + client_version, + customer_acceptance, + profile_id, + organization_id, + payment_method_type, + connector_payment_id, + payment_method_subtype, + authentication_applied, + external_reference_id, + id, + payment_method_id, + payment_method_billing_address, + connector, + connector_token_details, + card_discovery, + charges, + } = self; - let card_network = self - .payment_method_data + let card_network = payment_method_data .as_ref() .and_then(|data| data.peek().as_object()) .and_then(|card| card.get("card")) @@ -2034,70 +2098,70 @@ impl behaviour::Conversion for PaymentAttempt { .and_then(|network| network.as_str()) .map(|network| network.to_string()); - let error_details = self.error; + let error_details = error; Ok(DieselPaymentAttemptNew { - payment_id: self.payment_id, - merchant_id: self.merchant_id, - status: self.status, + payment_id, + merchant_id, + status, error_message: error_details .as_ref() .map(|details| details.message.clone()), - surcharge_amount: self.amount_details.surcharge_amount, - tax_on_surcharge: self.amount_details.tax_on_surcharge, - payment_method_id: self.payment_method_id, - authentication_type: self.authentication_type, - created_at: self.created_at, - modified_at: self.modified_at, - last_synced: self.last_synced, - cancellation_reason: self.cancellation_reason, - browser_info: self.browser_info, - payment_token: self.payment_token, + surcharge_amount: amount_details.surcharge_amount, + tax_on_surcharge: amount_details.tax_on_surcharge, + payment_method_id, + authentication_type, + created_at, + modified_at, + last_synced, + cancellation_reason, + browser_info, + payment_token, error_code: error_details.as_ref().map(|details| details.code.clone()), - connector_metadata: self.connector_metadata, - payment_experience: self.payment_experience, - payment_method_data: self.payment_method_data, - preprocessing_step_id: self.preprocessing_step_id, + connector_metadata, + payment_experience, + payment_method_data, + preprocessing_step_id, error_reason: error_details .as_ref() .and_then(|details| details.reason.clone()), - connector_response_reference_id: self.connector_response_reference_id, - multiple_capture_count: self.multiple_capture_count, - amount_capturable: self.amount_details.amount_capturable, - updated_by: self.updated_by, - merchant_connector_id: self.merchant_connector_id, - redirection_data: self.redirection_data.map(From::from), - encoded_data: self.encoded_data, + connector_response_reference_id, + multiple_capture_count, + amount_capturable: amount_details.amount_capturable, + updated_by, + merchant_connector_id, + redirection_data: redirection_data.map(From::from), + encoded_data, unified_code: error_details .as_ref() .and_then(|details| details.unified_code.clone()), unified_message: error_details .as_ref() .and_then(|details| details.unified_message.clone()), - net_amount: self.amount_details.net_amount, - external_three_ds_authentication_attempted: self - .external_three_ds_authentication_attempted, - authentication_connector: self.authentication_connector, - authentication_id: self.authentication_id, - fingerprint_id: self.fingerprint_id, - client_source: self.client_source, - client_version: self.client_version, - customer_acceptance: self.customer_acceptance, - profile_id: self.profile_id, - organization_id: self.organization_id, + net_amount: amount_details.net_amount, + external_three_ds_authentication_attempted, + authentication_connector, + authentication_id, + fingerprint_id, + client_source, + client_version, + customer_acceptance, + profile_id, + organization_id, card_network, - order_tax_amount: self.amount_details.order_tax_amount, - shipping_cost: self.amount_details.shipping_cost, - amount_to_capture: self.amount_details.amount_to_capture, - payment_method_billing_address: self - .payment_method_billing_address - .map(Encryption::from), - payment_method_subtype: self.payment_method_subtype, - payment_method_type_v2: self.payment_method_type, - id: self.id, - connector_token_details: self.connector_token_details, - card_discovery: self.card_discovery, - charges: self.charges, + order_tax_amount: amount_details.order_tax_amount, + shipping_cost: amount_details.shipping_cost, + amount_to_capture: amount_details.amount_to_capture, + payment_method_billing_address: payment_method_billing_address.map(Encryption::from), + payment_method_subtype, + payment_method_type_v2: payment_method_type, + id, + charges, + connector_token_details, + card_discovery, + extended_authorization_applied: None, + request_extended_authorization: None, + capture_before: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index b2335550cf5..f82eb782c50 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1406,6 +1406,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config, routing_algorithm_id, psd2_sca_exemption_type: None, + request_extended_authorization: None, platform_merchant_id, split_payments: None, }) @@ -1672,6 +1673,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + request_extended_authorization: self.request_extended_authorization, psd2_sca_exemption_type: self.psd2_sca_exemption_type, platform_merchant_id: self.platform_merchant_id, }) @@ -1761,6 +1763,7 @@ impl behaviour::Conversion for PaymentIntent { is_payment_processor_token_flow: storage_model.is_payment_processor_token_flow, organization_id: storage_model.organization_id, skip_external_tax_calculation: storage_model.skip_external_tax_calculation, + request_extended_authorization: storage_model.request_extended_authorization, psd2_sca_exemption_type: storage_model.psd2_sca_exemption_type, platform_merchant_id: storage_model.platform_merchant_id, }) @@ -1826,6 +1829,7 @@ impl behaviour::Conversion for PaymentIntent { shipping_cost: self.shipping_cost, tax_details: self.tax_details, skip_external_tax_calculation: self.skip_external_tax_calculation, + request_extended_authorization: self.request_extended_authorization, psd2_sca_exemption_type: self.psd2_sca_exemption_type, platform_merchant_id: self.platform_merchant_id, }) diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index cdca5883f4e..601fbd16238 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -3710,6 +3710,7 @@ impl ProfileCreateBridge for api::ProfileCreate { is_network_tokenization_enabled: self.is_network_tokenization_enabled, is_auto_retries_enabled: self.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: self.max_auto_retries_enabled.map(i16::from), + always_request_extended_authorization: self.always_request_extended_authorization, is_click_to_pay_enabled: self.is_click_to_pay_enabled, authentication_product_ids: self.authentication_product_ids, })) diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 39bbef1f3d7..6702b94c43b 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -3613,6 +3613,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -3684,6 +3685,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -3753,6 +3755,7 @@ mod tests { shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -4286,6 +4289,9 @@ impl AttemptType { organization_id: old_payment_attempt.organization_id, profile_id: old_payment_attempt.profile_id, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, } } diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 1fd9f2404ff..1d3a56162e9 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -1305,6 +1305,9 @@ impl PaymentCreate { organization_id: organization_id.clone(), profile_id, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, }, additional_pm_data, @@ -1513,6 +1516,7 @@ impl PaymentCreate { shipping_cost: request.shipping_cost, tax_details, skip_external_tax_calculation, + request_extended_authorization: None, psd2_sca_exemption_type: request.psd2_sca_exemption_type, platform_merchant_id: platform_merchant_account .map(|platform_merchant_account| platform_merchant_account.get_id().to_owned()), diff --git a/crates/router/src/core/payments/retry.rs b/crates/router/src/core/payments/retry.rs index 1a0a7ad9e5d..d113cfd55de 100644 --- a/crates/router/src/core/payments/retry.rs +++ b/crates/router/src/core/payments/retry.rs @@ -653,6 +653,9 @@ pub fn make_new_payment_attempt( fingerprint_id: Default::default(), customer_acceptance: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: old_payment_attempt.card_discovery, } } diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 49ff8eb35da..5f39ad71352 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2508,6 +2508,8 @@ where order_tax_amount, connector_mandate_id, shipping_cost: payment_intent.shipping_cost, + capture_before: payment_attempt.capture_before, + extended_authorization_applied: payment_attempt.extended_authorization_applied, card_discovery: payment_attempt.card_discovery, }; @@ -2762,6 +2764,8 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay updated: None, split_payments: None, frm_metadata: None, + capture_before: pa.capture_before, + extended_authorization_applied: pa.extended_authorization_applied, order_tax_amount: None, connector_mandate_id:None, shipping_cost: None, diff --git a/crates/router/src/types/api/admin.rs b/crates/router/src/types/api/admin.rs index 28533e33e17..289b56a7800 100644 --- a/crates/router/src/types/api/admin.rs +++ b/crates/router/src/types/api/admin.rs @@ -176,6 +176,7 @@ impl ForeignTryFrom for ProfileResponse { is_network_tokenization_enabled: item.is_network_tokenization_enabled, is_auto_retries_enabled: item.is_auto_retries_enabled, max_auto_retries_enabled: item.max_auto_retries_enabled, + always_request_extended_authorization: item.always_request_extended_authorization, is_click_to_pay_enabled: item.is_click_to_pay_enabled, authentication_product_ids: item.authentication_product_ids, }) @@ -375,6 +376,7 @@ pub async fn create_profile_from_merchant_account( is_network_tokenization_enabled: request.is_network_tokenization_enabled, is_auto_retries_enabled: request.is_auto_retries_enabled.unwrap_or_default(), max_auto_retries_enabled: request.max_auto_retries_enabled.map(i16::from), + always_request_extended_authorization: request.always_request_extended_authorization, is_click_to_pay_enabled: request.is_click_to_pay_enabled, authentication_product_ids: request.authentication_product_ids, })) diff --git a/crates/router/src/types/storage/payment_attempt.rs b/crates/router/src/types/storage/payment_attempt.rs index c82a921fe17..ce6c1e92bd7 100644 --- a/crates/router/src/types/storage/payment_attempt.rs +++ b/crates/router/src/types/storage/payment_attempt.rs @@ -218,6 +218,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; @@ -302,6 +305,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; let store = state @@ -399,6 +405,9 @@ mod tests { profile_id: common_utils::generate_profile_id_of_default_length(), organization_id: Default::default(), connector_mandate_detail: Default::default(), + request_extended_authorization: Default::default(), + extended_authorization_applied: Default::default(), + capture_before: Default::default(), card_discovery: Default::default(), }; let store = state diff --git a/crates/router/src/utils/user/sample_data.rs b/crates/router/src/utils/user/sample_data.rs index e189deb9487..8217f9e34cd 100644 --- a/crates/router/src/utils/user/sample_data.rs +++ b/crates/router/src/utils/user/sample_data.rs @@ -274,6 +274,7 @@ pub async fn generate_sample_data( shipping_cost: None, tax_details: None, skip_external_tax_calculation: None, + request_extended_authorization: None, psd2_sca_exemption_type: None, platform_merchant_id: None, }; @@ -361,6 +362,9 @@ pub async fn generate_sample_data( order_tax_amount: None, processor_transaction_data, connector_mandate_detail: None, + request_extended_authorization: None, + extended_authorization_applied: None, + capture_before: None, card_discovery: None, }; diff --git a/crates/router/tests/payments.rs b/crates/router/tests/payments.rs index 82871fcfe25..94dcba750af 100644 --- a/crates/router/tests/payments.rs +++ b/crates/router/tests/payments.rs @@ -448,6 +448,8 @@ async fn payments_create_core() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, @@ -713,6 +715,8 @@ async fn payments_create_core_adyen_no_redirect() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, diff --git a/crates/router/tests/payments2.rs b/crates/router/tests/payments2.rs index 63df5a2decf..66cd41dbc74 100644 --- a/crates/router/tests/payments2.rs +++ b/crates/router/tests/payments2.rs @@ -209,6 +209,8 @@ async fn payments_create_core() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, @@ -483,6 +485,8 @@ async fn payments_create_core_adyen_no_redirect() { split_payments: None, frm_metadata: None, merchant_order_reference_id: None, + capture_before: None, + extended_authorization_applied: None, order_tax_amount: None, connector_mandate_id: None, shipping_cost: None, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index c9d250d306b..a44624203bb 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -208,6 +208,9 @@ impl PaymentAttemptInterface for MockDb { organization_id: payment_attempt.organization_id, profile_id: payment_attempt.profile_id, connector_mandate_detail: payment_attempt.connector_mandate_detail, + request_extended_authorization: payment_attempt.request_extended_authorization, + extended_authorization_applied: payment_attempt.extended_authorization_applied, + capture_before: payment_attempt.capture_before, card_discovery: payment_attempt.card_discovery, charges: None, }; diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 7fb1d3ab80d..98569ea69ae 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -603,6 +603,9 @@ impl PaymentAttemptInterface for KVRouterStore { organization_id: payment_attempt.organization_id.clone(), profile_id: payment_attempt.profile_id.clone(), connector_mandate_detail: payment_attempt.connector_mandate_detail.clone(), + request_extended_authorization: payment_attempt.request_extended_authorization, + extended_authorization_applied: payment_attempt.extended_authorization_applied, + capture_before: payment_attempt.capture_before, card_discovery: payment_attempt.card_discovery, charges: None, }; @@ -1572,6 +1575,9 @@ impl DataModelExt for PaymentAttempt { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, processor_transaction_data, card_discovery: self.card_discovery, charges: self.charges, @@ -1653,6 +1659,9 @@ impl DataModelExt for PaymentAttempt { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, charges: storage_model.charges, } @@ -1737,6 +1746,9 @@ impl DataModelExt for PaymentAttemptNew { shipping_cost: self.net_amount.get_shipping_cost(), order_tax_amount: self.net_amount.get_order_tax_amount(), connector_mandate_detail: self.connector_mandate_detail, + request_extended_authorization: self.request_extended_authorization, + extended_authorization_applied: self.extended_authorization_applied, + capture_before: self.capture_before, card_discovery: self.card_discovery, } } @@ -1809,6 +1821,9 @@ impl DataModelExt for PaymentAttemptNew { organization_id: storage_model.organization_id, profile_id: storage_model.profile_id, connector_mandate_detail: storage_model.connector_mandate_detail, + request_extended_authorization: storage_model.request_extended_authorization, + extended_authorization_applied: storage_model.extended_authorization_applied, + capture_before: storage_model.capture_before, card_discovery: storage_model.card_discovery, } } diff --git a/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql new file mode 100644 index 00000000000..a5e982f8ac3 --- /dev/null +++ b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/down.sql @@ -0,0 +1,16 @@ +-- Remove the 'request_extended_authorization' column from the 'payment_intent' table +ALTER TABLE payment_intent +DROP COLUMN request_extended_authorization; + +-- Remove the 'request_extended_authorization' and 'extended_authorization_applied' columns from the 'payment_attempt' table +ALTER TABLE payment_attempt +DROP COLUMN request_extended_authorization, +DROP COLUMN extended_authorization_applied; + +-- Remove the 'capture_before' column from the 'payment_attempt' table +ALTER TABLE payment_attempt +DROP COLUMN capture_before; + +-- Remove the 'always_request_extended_authorization' column from the 'business_profile' table +ALTER TABLE business_profile +DROP COLUMN always_request_extended_authorization; \ No newline at end of file diff --git a/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql new file mode 100644 index 00000000000..ab5eac19411 --- /dev/null +++ b/migrations/2024-11-13-090548_add-extended-authorization-related-fields/up.sql @@ -0,0 +1,21 @@ +-- stores the flag send by the merchant during payments-create call +ALTER TABLE payment_intent +ADD COLUMN request_extended_authorization boolean; + + +ALTER TABLE payment_attempt +-- stores the flag sent to the connector +ADD COLUMN request_extended_authorization boolean; + +ALTER TABLE payment_attempt +-- Set to true if extended authentication request was successfully processed by the connector +ADD COLUMN extended_authorization_applied boolean; + + +ALTER TABLE payment_attempt +-- stores the flag sent to the connector +ADD COLUMN capture_before timestamp; + +ALTER TABLE business_profile +-- merchant can configure the default value for request_extended_authorization here +ADD COLUMN always_request_extended_authorization boolean; From d18d98a1f687aef1e0f21f6a26387cb9ca7a347d Mon Sep 17 00:00:00 2001 From: Swangi Kumari <85639103+swangi-kumari@users.noreply.github.com> Date: Tue, 18 Feb 2025 19:46:51 +0530 Subject: [PATCH 111/133] feat(connector): [Moneris] Add payments flow (#7249) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 2 + api-reference/openapi_spec.json | 2 + config/deployments/production.toml | 2 +- crates/common_enums/src/connector_enums.rs | 8 +- crates/connector_configs/src/connector.rs | 2 + .../connector_configs/toml/development.toml | 42 +++ crates/connector_configs/toml/production.toml | 42 +++ crates/connector_configs/toml/sandbox.toml | 42 +++ .../src/connectors/moneris.rs | 316 +++++++++++++++-- .../src/connectors/moneris/transformers.rs | 322 ++++++++++++++---- .../payment_connector_required_fields.rs | 94 +++++ crates/router/src/core/admin.rs | 4 + crates/router/src/types/api.rs | 3 + crates/router/src/types/transformers.rs | 2 +- crates/router/tests/connectors/moneris.rs | 2 +- crates/test_utils/src/connector_auth.rs | 2 +- .../cypress/e2e/configs/Payment/Moneris.js | 316 +++++++++++++++++ .../cypress/e2e/configs/Payment/Utils.js | 2 + 18 files changed, 1094 insertions(+), 111 deletions(-) create mode 100644 cypress-tests/cypress/e2e/configs/Payment/Moneris.js diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 9c4339b7e36..c91810a9902 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6993,6 +6993,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -19361,6 +19362,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 48e376791b3..587cba45e6a 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9559,6 +9559,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -24453,6 +24454,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 872845ceb02..79ed26906ef 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -78,7 +78,7 @@ klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" -moneris.base_url = "https://api.sb.moneris.io" +moneris.base_url = "https://api.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://api.payengine.de/v1" nexixpay.base_url = "https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 8a42bc274cb..c52bb6816d7 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -93,7 +93,7 @@ pub enum RoutableConnectors { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Nexinets, Nexixpay, @@ -233,7 +233,7 @@ pub enum Connector { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Netcetera, Nexinets, @@ -318,6 +318,7 @@ impl Connector { | (Self::Deutschebank, _) | (Self::Globalpay, _) | (Self::Jpmorgan, _) + | (Self::Moneris, _) | (Self::Paypal, _) | (Self::Payu, _) | (Self::Trustpay, PaymentMethod::BankRedirect) @@ -384,7 +385,7 @@ impl Connector { | Self::Klarna | Self::Mifinity | Self::Mollie - // | Self::Moneris + | Self::Moneris | Self::Multisafepay | Self::Nexinets | Self::Nexixpay @@ -513,6 +514,7 @@ impl From for Connector { RoutableConnectors::Klarna => Self::Klarna, RoutableConnectors::Mifinity => Self::Mifinity, RoutableConnectors::Mollie => Self::Mollie, + RoutableConnectors::Moneris => Self::Moneris, RoutableConnectors::Multisafepay => Self::Multisafepay, RoutableConnectors::Nexinets => Self::Nexinets, RoutableConnectors::Nexixpay => Self::Nexixpay, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index f0cea8d1496..8f1deb02c53 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -203,6 +203,7 @@ pub struct ConnectorConfig { pub klarna: Option, pub mifinity: Option, pub mollie: Option, + pub moneris: Option, pub multisafepay: Option, pub nexinets: Option, pub nexixpay: Option, @@ -367,6 +368,7 @@ impl ConnectorConfig { Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), + Connector::Moneris => Ok(connector_data.moneris), Connector::Multisafepay => Ok(connector_data.multisafepay), Connector::Nexinets => Ok(connector_data.nexinets), Connector::Nexixpay => Ok(connector_data.nexixpay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index c4897b2e788..266ae918779 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1878,6 +1878,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 9251edf9003..e4c0e70e191 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1588,6 +1588,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index c13349325db..b7b57690474 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1826,6 +1826,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/hyperswitch_connectors/src/connectors/moneris.rs b/crates/hyperswitch_connectors/src/connectors/moneris.rs index d543a0be72c..46b0ee9472e 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris.rs @@ -1,10 +1,11 @@ pub mod transformers; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -21,8 +22,8 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -33,23 +34,27 @@ use hyperswitch_interfaces::{ configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, RefreshTokenType, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, PeekInterface}; use transformers as moneris; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, RefundsRequestData}, +}; #[derive(Clone)] pub struct Moneris { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Moneris { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &MinorUnitForConnector, } } } @@ -82,12 +87,31 @@ where req: &RouterData, _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let auth = moneris::MonerisAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + moneris::auth_headers::API_VERSION.to_string(), + "2024-09-17".to_string().into(), + ), + ( + moneris::auth_headers::X_MERCHANT_ID.to_string(), + auth.merchant_id.expose().into_masked(), + ), + ]; + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.peek()).into_masked(), + ); + header.push(auth_header); Ok(header) } } @@ -117,7 +141,7 @@ impl ConnectorCommon for Moneris { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth.client_id.expose().into_masked(), )]) } @@ -134,11 +158,20 @@ impl ConnectorCommon for Moneris { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + let reason = match &response.errors { + Some(error_list) => error_list + .iter() + .map(|error| error.parameter_name.clone()) + .collect::>() + .join(" & "), + None => response.title.clone(), + }; + Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.category, + message: response.title, + reason: Some(reason), attempt_status: None, connector_transaction_id: None, }) @@ -146,16 +179,133 @@ impl ConnectorCommon for Moneris { } impl ConnectorValidation for Moneris { - //TODO: implement functions when support enabled + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled + | enums::CaptureMethod::SequentialAutomatic => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } } impl ConnectorIntegration for Moneris { //TODO: implement sessions flow } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/oauth2/token", self.base_url(connectors))) + } + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + fn get_headers( + &self, + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + RefreshTokenType::get_content_type(self).to_string().into(), + )]) + } + + fn get_request_body( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisAuthRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .url(&RefreshTokenType::get_url(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisAuthResponse = res + .response + .parse_struct("Moneris MonerisAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } -impl ConnectorIntegration for Moneris {} + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + // auth error have different structure than common error + let response: moneris::MonerisAuthErrorResponse = res + .response + .parse_struct("MonerisAuthErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.to_string(), + message: response.error.clone(), + reason: response.error_description, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorIntegration for Moneris { + // Not Implemented (R) + fn build_request( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Moneris".to_string()) + .into(), + ) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -173,9 +323,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payments", self.base_url(connectors))) } fn get_request_body( @@ -259,10 +409,18 @@ impl ConnectorIntegration for Mon fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/payments/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -323,18 +481,31 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/complete", + self.base_url(connectors) + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = moneris::MonerisRouterData::from((amount, req)); + let connector_req = + moneris::MonerisPaymentsCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -385,7 +556,77 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/cancel", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("Moneris PaymentsCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -403,9 +644,9 @@ impl ConnectorIntegration for Moneris fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds", self.base_url(connectors))) } fn get_request_body( @@ -486,10 +727,11 @@ impl ConnectorIntegration for Moneris { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req.request.get_connector_refund_id()?; + Ok(format!("{}/refunds/{refund_id}", self.base_url(connectors))) } fn build_request( diff --git a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs index 0cc255d85bf..b8fb3d0fc51 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs @@ -1,31 +1,38 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::MinorUnit; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{RefreshTokenRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + BrowserInformationData, CardData as _, PaymentsAuthorizeRequestData, + RouterData as OtherRouterData, + }, }; -//TODO: Fill the struct with respective fields +const CLIENT_CREDENTIALS: &str = "client_credentials"; + pub struct MonerisRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for MonerisRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(MinorUnit, T)> for MonerisRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +40,47 @@ impl From<(StringMinorUnit, T)> for MonerisRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] +pub mod auth_headers { + pub const X_MERCHANT_ID: &str = "X-Merchant-Id"; + pub const API_VERSION: &str = "Api-Version"; +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsRequest { - amount: StringMinorUnit, + idempotency_key: String, + amount: Amount, + payment_method: PaymentMethod, + automatic_capture: bool, + ipv4: Secret, +} +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + currency: enums::Currency, + amount: MinorUnit, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethod { + payment_method_source: PaymentMethodSource, card: MonerisCard, } +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaymentMethodSource { + Card, +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + card_number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + card_security_code: Secret, } impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest { @@ -54,18 +88,51 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen fn try_from( item: &MonerisRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "Moneris", + })? + }; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = MonerisCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, + PaymentMethodData::Card(ref req_card) => { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, }; + let payment_method = PaymentMethod { + payment_method_source: PaymentMethodSource::Card, + card: MonerisCard { + card_number: req_card.card_number.clone(), + expiry_month: Secret::new( + req_card + .card_exp_month + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + expiry_year: Secret::new( + req_card + .get_expiry_year_4_digit() + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + card_security_code: req_card.card_cvc.clone(), + }, + }; + let automatic_capture = item.router_data.request.is_auto_capture()?; + + let browser_info = item.router_data.request.get_browser_info()?; + let ipv4 = browser_info.get_ip_address()?; + Ok(Self { - amount: item.amount.clone(), - card, + idempotency_key, + amount, + payment_method, + automatic_capture, + ipv4, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -73,49 +140,106 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct MonerisAuthType { - pub(super) api_key: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, + pub(super) merchant_id: Secret, } impl TryFrom<&ConnectorAuthType> for MonerisAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + client_id: key1.to_owned(), + client_secret: api_key.to_owned(), + merchant_id: api_secret.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct MonerisAuthRequest { + client_id: Secret, + client_secret: Secret, + grant_type: String, +} + +impl TryFrom<&RefreshTokenRouterData> for MonerisAuthRequest { + type Error = error_stack::Report; + fn try_from(item: &RefreshTokenRouterData) -> Result { + let auth = MonerisAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + client_id: auth.client_id.clone(), + client_secret: auth.client_secret.clone(), + grant_type: CLIENT_CREDENTIALS.to_string(), + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MonerisAuthResponse { + access_token: Secret, + token_type: String, + expires_in: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(AccessToken { + token: item.response.access_token, + expires: item + .response + .expires_in + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, + }), + ..item.data + }) + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum MonerisPaymentStatus { Succeeded, - Failed, #[default] Processing, + Canceled, + Declined, + DeclinedRetry, + Authorized, } impl From for common_enums::AttemptStatus { fn from(item: MonerisPaymentStatus) -> Self { match item { MonerisPaymentStatus::Succeeded => Self::Charged, - MonerisPaymentStatus::Failed => Self::Failure, - MonerisPaymentStatus::Processing => Self::Authorizing, + MonerisPaymentStatus::Authorized => Self::Authorized, + MonerisPaymentStatus::Canceled => Self::Voided, + MonerisPaymentStatus::Declined | MonerisPaymentStatus::DeclinedRetry => Self::Failure, + MonerisPaymentStatus::Processing => Self::Pending, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsResponse { - status: MonerisPaymentStatus, - id: String, + payment_status: MonerisPaymentStatus, + payment_id: String, } impl TryFrom> @@ -126,14 +250,14 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: common_enums::AttemptStatus::from(item.response.payment_status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::ConnectorTransactionId(item.response.payment_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.payment_id), incremental_authorization_allowed: None, charges: None, }), @@ -142,50 +266,101 @@ impl TryFrom> for MonerisPaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &MonerisRouterData<&PaymentsCaptureRouterData>) -> Result { + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + Ok(Self { + amount, + idempotency_key, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MonerisCancelRequest { + idempotency_key: String, + reason: Option, +} + +impl TryFrom<&PaymentsCancelRouterData> for MonerisCancelRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelRouterData) -> Result { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.request.cancellation_reason.clone(); + Ok(Self { + idempotency_key, + reason, + }) + } +} + #[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct MonerisRefundRequest { - pub amount: StringMinorUnit, + pub refund_amount: Amount, + pub idempotency_key: String, + pub reason: Option, + pub payment_id: String, } impl TryFrom<&MonerisRouterData<&RefundsRouterData>> for MonerisRefundRequest { type Error = error_stack::Report; fn try_from(item: &MonerisRouterData<&RefundsRouterData>) -> Result { + let refund_amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.router_data.request.reason.clone(); + let payment_id = item.router_data.request.connector_transaction_id.clone(); Ok(Self { - amount: item.amount.to_owned(), + refund_amount, + idempotency_key, + reason, + payment_id, }) } } -// Type definition for Refund Response - #[allow(dead_code)] #[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RefundStatus { Succeeded, - Failed, #[default] Processing, + Declined, + DeclinedRetry, } impl From for enums::RefundStatus { fn from(item: RefundStatus) -> Self { match item { RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, + RefundStatus::Declined | RefundStatus::DeclinedRetry => Self::Failure, RefundStatus::Processing => Self::Pending, - //TODO: Review mapping } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + refund_id: String, + refund_status: RefundStatus, } impl TryFrom> for RefundsRouterData { @@ -195,8 +370,8 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) @@ -210,19 +385,32 @@ impl TryFrom> for RefundsRouter ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub status: u16, + pub category: String, + pub title: String, + pub errors: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MonerisError { + pub reason_code: String, + pub parameter_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MonerisAuthErrorResponse { + pub error: String, + pub error_description: Option, } diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index a9f5ae59493..27cc485188b 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -1997,6 +1997,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { @@ -5457,6 +5504,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 601fbd16238..98b55cd9823 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1433,6 +1433,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { mollie::transformers::MollieAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Moneris => { + moneris::transformers::MonerisAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Multisafepay => { multisafepay::transformers::MultisafepayAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 1a702cb835b..43556eb581a 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -464,6 +464,9 @@ impl ConnectorData { // enums::Connector::Moneris => Ok(ConnectorEnum::Old(Box::new(connector::Moneris))), Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) } + enums::Connector::Moneris => { + Ok(ConnectorEnum::Old(Box::new(connector::Moneris::new()))) + } enums::Connector::Nexixpay => { Ok(ConnectorEnum::Old(Box::new(connector::Nexixpay::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 7ac8792b507..aa27e362df2 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -263,7 +263,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, - // api_enums::Connector::Moneris => Self::Moneris, + api_enums::Connector::Moneris => Self::Moneris, api_enums::Connector::Multisafepay => Self::Multisafepay, api_enums::Connector::Netcetera => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/crates/router/tests/connectors/moneris.rs b/crates/router/tests/connectors/moneris.rs index ac98cc17f3d..ba8c7e491f3 100644 --- a/crates/router/tests/connectors/moneris.rs +++ b/crates/router/tests/connectors/moneris.rs @@ -13,7 +13,7 @@ impl utils::Connector for MonerisTest { use router::connector::Moneris; utils::construct_connector_data_old( Box::new(Moneris::new()), - types::Connector::Plaid, + types::Connector::Moneris, api::GetToken::Connector, None, ) diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 30de321a4b6..7a5b48f03a6 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -57,7 +57,7 @@ pub struct ConnectorAuthentication { pub jpmorgan: Option, pub mifinity: Option, pub mollie: Option, - pub moneris: Option, + pub moneris: Option, pub multisafepay: Option, pub netcetera: Option, pub nexinets: Option, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Moneris.js b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js new file mode 100644 index 00000000000..7382c6a25d5 --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js @@ -0,0 +1,316 @@ +import { getCustomExchange } from "./Modifiers"; + +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "John", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "Joseph", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + amount: 5000, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + amount: 5000, + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + amount: 5000, + shipping_cost: 50, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 5050, + amount: 5000, + net_amount: 5050, + }, + }, + }, + "3DSManualCapture": getCustomExchange({ + Request: { + amount: 5000, + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + "3DSAutoCapture": getCustomExchange({ + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + No3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + Capture: { + Request: { + amount_to_capture: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 5000, + amount_capturable: 0, + amount_received: 5000, + }, + }, + }, + PartialCapture: { + Request: { + amount_to_capture: 2000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 5000, + amount_capturable: 0, + amount_received: 2000, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthMandate: { + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + 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", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + 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", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index b3de4db78d4..8a7b559b95e 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -14,6 +14,7 @@ import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; import { connectorDetails as jpmorganConnectorDetails } from "./Jpmorgan.js"; +import { connectorDetails as monerisConnectorDetails } from "./Moneris.js"; import { connectorDetails as nexixpayConnectorDetails } from "./Nexixpay.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as noonConnectorDetails } from "./Noon.js"; @@ -39,6 +40,7 @@ const connectorDetails = { iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, jpmorgan: jpmorganConnectorDetails, + moneris: monerisConnectorDetails, nexixpay: nexixpayConnectorDetails, nmi: nmiConnectorDetails, novalnet: novalnetConnectorDetails, From d6e13dd0c87537e6696dd6dfc02280f825d116ab Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Feb 2025 00:27:19 +0000 Subject: [PATCH 112/133] chore(version): 2025.02.19.0 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704fda9678b..28565b08385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,18 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.19.0 + +### Features + +- **connector:** [Moneris] Add payments flow ([#7249](https://github.com/juspay/hyperswitch/pull/7249)) ([`d18d98a`](https://github.com/juspay/hyperswitch/commit/d18d98a1f687aef1e0f21f6a26387cb9ca7a347d)) +- **core:** Api ,domain and diesel model changes for extended authorization ([#6607](https://github.com/juspay/hyperswitch/pull/6607)) ([`e14d6c4`](https://github.com/juspay/hyperswitch/commit/e14d6c4465bb1276a348a668051c084af72de8e3)) +- **payments:** [Payment links] Add configs for payment link ([#7288](https://github.com/juspay/hyperswitch/pull/7288)) ([`72080c6`](https://github.com/juspay/hyperswitch/commit/72080c67c7927b53d5ca013983f379e9b027c51f)) + +**Full Changelog:** [`2025.02.18.0...2025.02.19.0`](https://github.com/juspay/hyperswitch/compare/2025.02.18.0...2025.02.19.0) + +- - - + ## 2025.02.18.0 ### Features From 22633be55cfc42dc4a7171c3193da594d0557bfb Mon Sep 17 00:00:00 2001 From: Uzair Khan Date: Wed, 19 Feb 2025 13:23:26 +0530 Subject: [PATCH 113/133] feat(core): add hypersense integration api (#7218) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/api_models/src/events.rs | 1 + .../src/events/external_service_auth.rs | 30 ++++++ .../api_models/src/external_service_auth.rs | 35 +++++++ crates/api_models/src/lib.rs | 1 + crates/common_utils/src/events.rs | 1 + crates/router/src/core.rs | 1 + .../router/src/core/external_service_auth.rs | 94 +++++++++++++++++++ crates/router/src/lib.rs | 1 + crates/router/src/routes.rs | 7 +- crates/router/src/routes/app.rs | 22 +++++ crates/router/src/routes/hypersense.rs | 76 +++++++++++++++ crates/router/src/routes/lock_utils.rs | 5 + crates/router/src/services/authentication.rs | 52 +++++++++- crates/router_env/src/logger/types.rs | 6 ++ 14 files changed, 326 insertions(+), 6 deletions(-) create mode 100644 crates/api_models/src/events/external_service_auth.rs create mode 100644 crates/api_models/src/external_service_auth.rs create mode 100644 crates/router/src/core/external_service_auth.rs create mode 100644 crates/router/src/routes/hypersense.rs diff --git a/crates/api_models/src/events.rs b/crates/api_models/src/events.rs index 2124ff1aff9..9a2a0ca8396 100644 --- a/crates/api_models/src/events.rs +++ b/crates/api_models/src/events.rs @@ -2,6 +2,7 @@ pub mod apple_pay_certificates_migration; pub mod connector_onboarding; pub mod customer; pub mod dispute; +pub mod external_service_auth; pub mod gsm; mod locker_migration; pub mod payment; diff --git a/crates/api_models/src/events/external_service_auth.rs b/crates/api_models/src/events/external_service_auth.rs new file mode 100644 index 00000000000..31196150b2c --- /dev/null +++ b/crates/api_models/src/events/external_service_auth.rs @@ -0,0 +1,30 @@ +use common_utils::events::{ApiEventMetric, ApiEventsType}; + +use crate::external_service_auth::{ + ExternalSignoutTokenRequest, ExternalTokenResponse, ExternalVerifyTokenRequest, + ExternalVerifyTokenResponse, +}; + +impl ApiEventMetric for ExternalTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalVerifyTokenRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalVerifyTokenResponse { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} + +impl ApiEventMetric for ExternalSignoutTokenRequest { + fn get_api_event_type(&self) -> Option { + Some(ApiEventsType::ExternalServiceAuth) + } +} diff --git a/crates/api_models/src/external_service_auth.rs b/crates/api_models/src/external_service_auth.rs new file mode 100644 index 00000000000..775529c4359 --- /dev/null +++ b/crates/api_models/src/external_service_auth.rs @@ -0,0 +1,35 @@ +use common_utils::{id_type, pii}; +use masking::Secret; + +#[derive(Debug, serde::Serialize)] +pub struct ExternalTokenResponse { + pub token: Secret, +} +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ExternalVerifyTokenRequest { + pub token: Secret, +} + +#[derive(Debug, serde::Serialize, serde::Deserialize)] +pub struct ExternalSignoutTokenRequest { + pub token: Secret, +} + +#[derive(serde::Serialize, Debug)] +#[serde(untagged)] +pub enum ExternalVerifyTokenResponse { + Hypersense { + user_id: String, + merchant_id: id_type::MerchantId, + name: Secret, + email: pii::Email, + }, +} + +impl ExternalVerifyTokenResponse { + pub fn get_user_id(&self) -> &str { + match self { + Self::Hypersense { user_id, .. } => user_id, + } + } +} diff --git a/crates/api_models/src/lib.rs b/crates/api_models/src/lib.rs index 5ec80d2b539..c3cf1f1d25a 100644 --- a/crates/api_models/src/lib.rs +++ b/crates/api_models/src/lib.rs @@ -16,6 +16,7 @@ pub mod ephemeral_key; #[cfg(feature = "errors")] pub mod errors; pub mod events; +pub mod external_service_auth; pub mod feature_matrix; pub mod files; pub mod gsm; diff --git a/crates/common_utils/src/events.rs b/crates/common_utils/src/events.rs index 2e90d646e9e..556090858fb 100644 --- a/crates/common_utils/src/events.rs +++ b/crates/common_utils/src/events.rs @@ -99,6 +99,7 @@ pub enum ApiEventsType { ApplePayCertificatesMigration, FraudCheck, Recon, + ExternalServiceAuth, Dispute { dispute_id: String, }, diff --git a/crates/router/src/core.rs b/crates/router/src/core.rs index c22cecc20f9..18b3ad14358 100644 --- a/crates/router/src/core.rs +++ b/crates/router/src/core.rs @@ -18,6 +18,7 @@ pub mod customers; pub mod disputes; pub mod encryption; pub mod errors; +pub mod external_service_auth; pub mod files; #[cfg(feature = "frm")] pub mod fraud_check; diff --git a/crates/router/src/core/external_service_auth.rs b/crates/router/src/core/external_service_auth.rs new file mode 100644 index 00000000000..92c8f5b249d --- /dev/null +++ b/crates/router/src/core/external_service_auth.rs @@ -0,0 +1,94 @@ +use api_models::external_service_auth as external_service_auth_api; +use common_utils::fp_utils; +use error_stack::ResultExt; +use masking::ExposeInterface; + +use crate::{ + core::errors::{self, RouterResponse}, + services::{ + api as service_api, + authentication::{self, ExternalServiceType, ExternalToken}, + }, + SessionState, +}; + +pub async fn generate_external_token( + state: SessionState, + user: authentication::UserFromToken, + external_service_type: ExternalServiceType, +) -> RouterResponse { + let token = ExternalToken::new_token( + user.user_id.clone(), + user.merchant_id.clone(), + &state.conf, + external_service_type.clone(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable_lazy(|| { + format!( + "Failed to create external token for params [user_id, mid, external_service_type] [{}, {:?}, {:?}]", + user.user_id, user.merchant_id, external_service_type, + ) + })?; + + Ok(service_api::ApplicationResponse::Json( + external_service_auth_api::ExternalTokenResponse { + token: token.into(), + }, + )) +} + +pub async fn signout_external_token( + state: SessionState, + json_payload: external_service_auth_api::ExternalSignoutTokenRequest, +) -> RouterResponse<()> { + let token = authentication::decode_jwt::(&json_payload.token.expose(), &state) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + authentication::blacklist::insert_user_in_blacklist(&state, &token.user_id) + .await + .change_context(errors::ApiErrorResponse::InvalidJwtToken)?; + + Ok(service_api::ApplicationResponse::StatusOk) +} + +pub async fn verify_external_token( + state: SessionState, + json_payload: external_service_auth_api::ExternalVerifyTokenRequest, + external_service_type: ExternalServiceType, +) -> RouterResponse { + let token_from_payload = json_payload.token.expose(); + + let token = authentication::decode_jwt::(&token_from_payload, &state) + .await + .change_context(errors::ApiErrorResponse::Unauthorized)?; + + fp_utils::when( + authentication::blacklist::check_user_in_blacklist(&state, &token.user_id, token.exp) + .await?, + || Err(errors::ApiErrorResponse::InvalidJwtToken), + )?; + + token.check_service_type(&external_service_type)?; + + let user_in_db = state + .global_store + .find_user_by_id(&token.user_id) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("User not found in database")?; + + let email = user_in_db.email.clone(); + let name = user_in_db.name; + + Ok(service_api::ApplicationResponse::Json( + external_service_auth_api::ExternalVerifyTokenResponse::Hypersense { + user_id: user_in_db.user_id, + merchant_id: token.merchant_id, + name, + email, + }, + )) +} diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 9b43d7f2b16..18e8e7cdccf 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -144,6 +144,7 @@ pub fn mk_app( .service(routes::MerchantConnectorAccount::server(state.clone())) .service(routes::RelayWebhooks::server(state.clone())) .service(routes::Webhooks::server(state.clone())) + .service(routes::Hypersense::server(state.clone())) .service(routes::Relay::server(state.clone())); #[cfg(feature = "oltp")] diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index 80906570d7c..b589d4755f8 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -23,6 +23,7 @@ pub mod files; pub mod fraud_check; pub mod gsm; pub mod health; +pub mod hypersense; pub mod lock_utils; #[cfg(feature = "v1")] pub mod locker_migration; @@ -69,9 +70,9 @@ pub use self::app::PaymentMethodsSession; pub use self::app::Recon; pub use self::app::{ ApiKeys, AppState, ApplePayCertificatesMigration, Cache, Cards, Configs, ConnectorOnboarding, - Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Mandates, - MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, Poll, - Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, Webhooks, + Customers, Disputes, EphemeralKey, FeatureMatrix, Files, Forex, Gsm, Health, Hypersense, + Mandates, MerchantAccount, MerchantConnectorAccount, PaymentLink, PaymentMethods, Payments, + Poll, Profile, ProfileNew, Refunds, Relay, RelayWebhooks, SessionState, User, Webhooks, }; #[cfg(feature = "olap")] pub use self::app::{Blocklist, Organization, Routing, Verify, WebhookEvents}; diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 3da65e968b6..14372cbfbba 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -88,6 +88,7 @@ pub use crate::{ use crate::{ configs::{secrets_transformers, Settings}, db::kafka_store::{KafkaStore, TenantID}, + routes::hypersense as hypersense_routes, }; #[derive(Clone)] @@ -1330,6 +1331,27 @@ impl Recon { } } +pub struct Hypersense; + +impl Hypersense { + pub fn server(state: AppState) -> Scope { + web::scope("/hypersense") + .app_data(web::Data::new(state)) + .service( + web::resource("/token") + .route(web::get().to(hypersense_routes::get_hypersense_token)), + ) + .service( + web::resource("/verify_token") + .route(web::post().to(hypersense_routes::verify_hypersense_token)), + ) + .service( + web::resource("/signout") + .route(web::post().to(hypersense_routes::signout_hypersense_token)), + ) + } +} + #[cfg(feature = "olap")] pub struct Blocklist; diff --git a/crates/router/src/routes/hypersense.rs b/crates/router/src/routes/hypersense.rs new file mode 100644 index 00000000000..a018dfa6605 --- /dev/null +++ b/crates/router/src/routes/hypersense.rs @@ -0,0 +1,76 @@ +use actix_web::{web, HttpRequest, HttpResponse}; +use api_models::external_service_auth as external_service_auth_api; +use router_env::Flow; + +use super::AppState; +use crate::{ + core::{api_locking, external_service_auth}, + services::{ + api, + authentication::{self, ExternalServiceType}, + }, +}; + +pub async fn get_hypersense_token(state: web::Data, req: HttpRequest) -> HttpResponse { + let flow = Flow::HypersenseTokenRequest; + Box::pin(api::server_wrap( + flow, + state, + &req, + (), + |state, user, _, _| { + external_service_auth::generate_external_token( + state, + user, + ExternalServiceType::Hypersense, + ) + }, + &authentication::DashboardNoPermissionAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn signout_hypersense_token( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::HypersenseSignoutToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _: (), json_payload, _| { + external_service_auth::signout_external_token(state, json_payload) + }, + &authentication::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} + +pub async fn verify_hypersense_token( + state: web::Data, + http_req: HttpRequest, + json_payload: web::Json, +) -> HttpResponse { + let flow = Flow::HypersenseVerifyToken; + Box::pin(api::server_wrap( + flow, + state.clone(), + &http_req, + json_payload.into_inner(), + |state, _: (), json_payload, _| { + external_service_auth::verify_external_token( + state, + json_payload, + ExternalServiceType::Hypersense, + ) + }, + &authentication::NoAuth, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index ce9dda97c9f..a50a27b9ec4 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -39,6 +39,7 @@ pub enum ApiIdentifier { ApplePayCertificatesMigration, Relay, Documentation, + Hypersense, PaymentMethodsSession, } @@ -311,6 +312,10 @@ impl From for ApiIdentifier { Flow::FeatureMatrix => Self::Documentation, + Flow::HypersenseTokenRequest + | Flow::HypersenseVerifyToken + | Flow::HypersenseSignoutToken => Self::Hypersense, + Flow::PaymentMethodSessionCreate | Flow::PaymentMethodSessionRetrieve | Flow::PaymentMethodSessionUpdateSavedPaymentMethod => Self::PaymentMethodsSession, diff --git a/crates/router/src/services/authentication.rs b/crates/router/src/services/authentication.rs index edf127ac30f..502ebb5099f 100644 --- a/crates/router/src/services/authentication.rs +++ b/crates/router/src/services/authentication.rs @@ -13,9 +13,7 @@ use api_models::payouts; use api_models::{payment_methods::PaymentMethodListRequest, payments}; use async_trait::async_trait; use common_enums::TokenPurpose; -#[cfg(feature = "v2")] -use common_utils::fp_utils; -use common_utils::{date_time, id_type}; +use common_utils::{date_time, fp_utils, id_type}; #[cfg(feature = "v2")] use diesel_models::ephemeral_key; use error_stack::{report, ResultExt}; @@ -195,6 +193,13 @@ impl AuthenticationType { } } +#[derive(Clone, Debug, Eq, PartialEq, Serialize, serde::Deserialize, strum::Display)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum ExternalServiceType { + Hypersense, +} + #[cfg(feature = "olap")] #[derive(Clone, Debug)] pub struct UserFromSinglePurposeToken { @@ -3857,3 +3862,44 @@ impl ReconToken { jwt::generate_jwt(&token_payload, settings).await } } +#[derive(serde::Serialize, serde::Deserialize)] +pub struct ExternalToken { + pub user_id: String, + pub merchant_id: id_type::MerchantId, + pub exp: u64, + pub external_service_type: ExternalServiceType, +} + +impl ExternalToken { + pub async fn new_token( + user_id: String, + merchant_id: id_type::MerchantId, + settings: &Settings, + external_service_type: ExternalServiceType, + ) -> 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, + merchant_id, + exp, + external_service_type, + }; + jwt::generate_jwt(&token_payload, settings).await + } + + pub fn check_service_type( + &self, + required_service_type: &ExternalServiceType, + ) -> RouterResult<()> { + Ok(fp_utils::when( + &self.external_service_type != required_service_type, + || { + Err(errors::ApiErrorResponse::AccessForbidden { + resource: required_service_type.to_string(), + }) + }, + )?) + } +} diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index b88effea34f..3eda63bdbaa 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -543,6 +543,12 @@ pub enum Flow { RelayRetrieve, /// Incoming Relay Webhook Receive IncomingRelayWebhookReceive, + /// Generate Hypersense Token + HypersenseTokenRequest, + /// Verify Hypersense Token + HypersenseVerifyToken, + /// Signout Hypersense Token + HypersenseSignoutToken, /// Payment Method Session Create PaymentMethodSessionCreate, /// Payment Method Session Retrieve From f3ca2009c1902094a72b8bf43e89b406e44ecfd4 Mon Sep 17 00:00:00 2001 From: Sakil Mostak <73734619+Sakilmostak@users.noreply.github.com> Date: Wed, 19 Feb 2025 16:30:14 +0530 Subject: [PATCH 114/133] refactor(utils): match string for state with SDK's naming convention (#7300) Co-authored-by: Debarati --- crates/common_enums/src/enums.rs | 1094 ++++-- crates/hyperswitch_connectors/src/utils.rs | 4040 ++++++++++---------- 2 files changed, 2879 insertions(+), 2255 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index c3421185e79..54514f6e05a 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -2943,128 +2943,274 @@ pub enum FinlandStatesAbbreviation { Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum FranceStatesAbbreviation { - #[strum(serialize = "WF-AL")] - Alo, - #[strum(serialize = "A")] + #[strum(serialize = "01")] + Ain, + #[strum(serialize = "02")] + Aisne, + #[strum(serialize = "03")] + Allier, + #[strum(serialize = "04")] + AlpesDeHauteProvence, + #[strum(serialize = "06")] + AlpesMaritimes, + #[strum(serialize = "6AE")] Alsace, - #[strum(serialize = "B")] - Aquitaine, - #[strum(serialize = "C")] - Auvergne, + #[strum(serialize = "07")] + Ardeche, + #[strum(serialize = "08")] + Ardennes, + #[strum(serialize = "09")] + Ariege, + #[strum(serialize = "10")] + Aube, + #[strum(serialize = "11")] + Aude, #[strum(serialize = "ARA")] AuvergneRhoneAlpes, + #[strum(serialize = "12")] + Aveyron, + #[strum(serialize = "67")] + BasRhin, + #[strum(serialize = "13")] + BouchesDuRhone, #[strum(serialize = "BFC")] BourgogneFrancheComte, #[strum(serialize = "BRE")] - Brittany, - #[strum(serialize = "D")] - Burgundy, + Bretagne, + #[strum(serialize = "14")] + Calvados, + #[strum(serialize = "15")] + Cantal, #[strum(serialize = "CVL")] CentreValDeLoire, - #[strum(serialize = "G")] - ChampagneArdenne, - #[strum(serialize = "COR")] - Corsica, - #[strum(serialize = "I")] - FrancheComte, - #[strum(serialize = "GF")] + #[strum(serialize = "16")] + Charente, + #[strum(serialize = "17")] + CharenteMaritime, + #[strum(serialize = "18")] + Cher, + #[strum(serialize = "CP")] + Clipperton, + #[strum(serialize = "19")] + Correze, + #[strum(serialize = "20R")] + Corse, + #[strum(serialize = "2A")] + CorseDuSud, + #[strum(serialize = "21")] + CoteDor, + #[strum(serialize = "22")] + CotesDarmor, + #[strum(serialize = "23")] + Creuse, + #[strum(serialize = "79")] + DeuxSevres, + #[strum(serialize = "24")] + Dordogne, + #[strum(serialize = "25")] + Doubs, + #[strum(serialize = "26")] + Drome, + #[strum(serialize = "91")] + Essonne, + #[strum(serialize = "27")] + Eure, + #[strum(serialize = "28")] + EureEtLoir, + #[strum(serialize = "29")] + Finistere, + #[strum(serialize = "973")] FrenchGuiana, #[strum(serialize = "PF")] FrenchPolynesia, + #[strum(serialize = "TF")] + FrenchSouthernAndAntarcticLands, + #[strum(serialize = "30")] + Gard, + #[strum(serialize = "32")] + Gers, + #[strum(serialize = "33")] + Gironde, #[strum(serialize = "GES")] GrandEst, - #[strum(serialize = "GP")] + #[strum(serialize = "971")] Guadeloupe, + #[strum(serialize = "68")] + HautRhin, + #[strum(serialize = "2B")] + HauteCorse, + #[strum(serialize = "31")] + HauteGaronne, + #[strum(serialize = "43")] + HauteLoire, + #[strum(serialize = "52")] + HauteMarne, + #[strum(serialize = "70")] + HauteSaone, + #[strum(serialize = "74")] + HauteSavoie, + #[strum(serialize = "87")] + HauteVienne, + #[strum(serialize = "05")] + HautesAlpes, + #[strum(serialize = "65")] + HautesPyrenees, #[strum(serialize = "HDF")] HautsDeFrance, - #[strum(serialize = "K")] - LanguedocRoussillon, - #[strum(serialize = "L")] - Limousin, - #[strum(serialize = "M")] - Lorraine, - #[strum(serialize = "P")] - LowerNormandy, - #[strum(serialize = "MQ")] + #[strum(serialize = "92")] + HautsDeSeine, + #[strum(serialize = "34")] + Herault, + #[strum(serialize = "IDF")] + IleDeFrance, + #[strum(serialize = "35")] + IlleEtVilaine, + #[strum(serialize = "36")] + Indre, + #[strum(serialize = "37")] + IndreEtLoire, + #[strum(serialize = "38")] + Isere, + #[strum(serialize = "39")] + Jura, + #[strum(serialize = "974")] + LaReunion, + #[strum(serialize = "40")] + Landes, + #[strum(serialize = "41")] + LoirEtCher, + #[strum(serialize = "42")] + Loire, + #[strum(serialize = "44")] + LoireAtlantique, + #[strum(serialize = "45")] + Loiret, + #[strum(serialize = "46")] + Lot, + #[strum(serialize = "47")] + LotEtGaronne, + #[strum(serialize = "48")] + Lozere, + #[strum(serialize = "49")] + MaineEtLoire, + #[strum(serialize = "50")] + Manche, + #[strum(serialize = "51")] + Marne, + #[strum(serialize = "972")] Martinique, - #[strum(serialize = "YT")] + #[strum(serialize = "53")] + Mayenne, + #[strum(serialize = "976")] Mayotte, - #[strum(serialize = "O")] - NordPasDeCalais, + #[strum(serialize = "69M")] + MetropoleDeLyon, + #[strum(serialize = "54")] + MeurtheEtMoselle, + #[strum(serialize = "55")] + Meuse, + #[strum(serialize = "56")] + Morbihan, + #[strum(serialize = "57")] + Moselle, + #[strum(serialize = "58")] + Nievre, + #[strum(serialize = "59")] + Nord, #[strum(serialize = "NOR")] - Normandy, + Normandie, #[strum(serialize = "NAQ")] NouvelleAquitaine, #[strum(serialize = "OCC")] - Occitania, - #[strum(serialize = "75")] + Occitanie, + #[strum(serialize = "60")] + Oise, + #[strum(serialize = "61")] + Orne, + #[strum(serialize = "75C")] Paris, + #[strum(serialize = "62")] + PasDeCalais, #[strum(serialize = "PDL")] PaysDeLaLoire, - #[strum(serialize = "S")] - Picardy, - #[strum(serialize = "T")] - PoitouCharentes, #[strum(serialize = "PAC")] - ProvenceAlpesCoteDAzur, - #[strum(serialize = "V")] - RhoneAlpes, - #[strum(serialize = "RE")] - Reunion, + ProvenceAlpesCoteDazur, + #[strum(serialize = "63")] + PuyDeDome, + #[strum(serialize = "64")] + PyreneesAtlantiques, + #[strum(serialize = "66")] + PyreneesOrientales, + #[strum(serialize = "69")] + Rhone, + #[strum(serialize = "PM")] + SaintPierreAndMiquelon, #[strum(serialize = "BL")] SaintBarthelemy, #[strum(serialize = "MF")] SaintMartin, - #[strum(serialize = "PM")] - SaintPierreAndMiquelon, - #[strum(serialize = "WF-SG")] - Sigave, - #[strum(serialize = "Q")] - UpperNormandy, - #[strum(serialize = "WF-UV")] - Uvea, + #[strum(serialize = "71")] + SaoneEtLoire, + #[strum(serialize = "72")] + Sarthe, + #[strum(serialize = "73")] + Savoie, + #[strum(serialize = "77")] + SeineEtMarne, + #[strum(serialize = "76")] + SeineMaritime, + #[strum(serialize = "93")] + SeineSaintDenis, + #[strum(serialize = "80")] + Somme, + #[strum(serialize = "81")] + Tarn, + #[strum(serialize = "82")] + TarnEtGaronne, + #[strum(serialize = "90")] + TerritoireDeBelfort, + #[strum(serialize = "95")] + ValDoise, + #[strum(serialize = "94")] + ValDeMarne, + #[strum(serialize = "83")] + Var, + #[strum(serialize = "84")] + Vaucluse, + #[strum(serialize = "85")] + Vendee, + #[strum(serialize = "86")] + Vienne, + #[strum(serialize = "88")] + Vosges, #[strum(serialize = "WF")] WallisAndFutuna, - #[strum(serialize = "IDF")] - IleDeFrance, + #[strum(serialize = "89")] + Yonne, + #[strum(serialize = "78")] + Yvelines, } #[derive( Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum GermanyStatesAbbreviation { - #[strum(serialize = "BW")] - BadenWurttemberg, - #[strum(serialize = "BY")] - Bavaria, - #[strum(serialize = "BE")] - Berlin, - #[strum(serialize = "BB")] - Brandenburg, - #[strum(serialize = "HB")] - Bremen, - #[strum(serialize = "HH")] - Hamburg, - #[strum(serialize = "HE")] - Hesse, - #[strum(serialize = "NI")] - LowerSaxony, - #[strum(serialize = "MV")] - MecklenburgVorpommern, - #[strum(serialize = "NW")] - NorthRhineWestphalia, - #[strum(serialize = "RP")] - RhinelandPalatinate, - #[strum(serialize = "SL")] - Saarland, - #[strum(serialize = "SN")] - Saxony, - #[strum(serialize = "ST")] - SaxonyAnhalt, - #[strum(serialize = "SH")] - SchleswigHolstein, - #[strum(serialize = "TH")] - Thuringia, + BW, + BY, + BE, + BB, + HB, + HH, + HE, + NI, + MV, + NW, + RP, + SL, + SN, + ST, + SH, + TH, } #[derive( @@ -3802,7 +3948,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "04")] Birkirkara, #[strum(serialize = "05")] - Birzebbuga, + Birżebbuġa, #[strum(serialize = "06")] Cospicua, #[strum(serialize = "07")] @@ -3816,19 +3962,19 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "11")] Gudja, #[strum(serialize = "12")] - Gzira, + Gżira, #[strum(serialize = "13")] - Ghajnsielem, + Għajnsielem, #[strum(serialize = "14")] - Gharb, + Għarb, #[strum(serialize = "15")] - Gharghur, + Għargħur, #[strum(serialize = "16")] - Ghasri, + Għasri, #[strum(serialize = "17")] - Ghaxaq, + Għaxaq, #[strum(serialize = "18")] - Hamrun, + Ħamrun, #[strum(serialize = "19")] Iklin, #[strum(serialize = "20")] @@ -3836,7 +3982,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "21")] Kalkara, #[strum(serialize = "22")] - Kercem, + Kerċem, #[strum(serialize = "23")] Kirkop, #[strum(serialize = "24")] @@ -3852,9 +3998,9 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "29")] Mdina, #[strum(serialize = "30")] - Mellieha, + Mellieħa, #[strum(serialize = "31")] - Mgarr, + Mġarr, #[strum(serialize = "32")] Mosta, #[strum(serialize = "33")] @@ -3874,7 +4020,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "40")] Pembroke, #[strum(serialize = "41")] - Pieta, + Pietà, #[strum(serialize = "42")] Qala, #[strum(serialize = "43")] @@ -3888,7 +4034,7 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "48")] StJulians, #[strum(serialize = "49")] - SanGwann, + SanĠwann, #[strum(serialize = "50")] SaintLawrence, #[strum(serialize = "51")] @@ -3896,11 +4042,11 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "52")] Sannat, #[strum(serialize = "53")] - SantaLucija, + SantaLuċija, #[strum(serialize = "54")] SantaVenera, #[strum(serialize = "55")] - Siggiewi, + Siġġiewi, #[strum(serialize = "56")] Sliema, #[strum(serialize = "57")] @@ -3912,21 +4058,21 @@ pub enum MaltaStatesAbbreviation { #[strum(serialize = "60")] Valletta, #[strum(serialize = "61")] - Xaghra, + Xagħra, #[strum(serialize = "62")] Xewkija, #[strum(serialize = "63")] - Xghajra, + Xgħajra, #[strum(serialize = "64")] - Zabbar, + Żabbar, #[strum(serialize = "65")] - ZebbugGozo, + ŻebbuġGozo, #[strum(serialize = "66")] - ZebbugMalta, + ŻebbuġMalta, #[strum(serialize = "67")] - Zejtun, + Żejtun, #[strum(serialize = "68")] - Zurrieq, + Żurrieq, } #[derive( @@ -3942,69 +4088,69 @@ pub enum MoldovaStatesAbbreviation { #[strum(serialize = "BR")] BriceniDistrict, #[strum(serialize = "BA")] - BaltiMunicipality, + BălțiMunicipality, #[strum(serialize = "CA")] CahulDistrict, #[strum(serialize = "CT")] CantemirDistrict, #[strum(serialize = "CU")] - ChisinauMunicipality, + ChișinăuMunicipality, #[strum(serialize = "CM")] - CimisliaDistrict, + CimișliaDistrict, #[strum(serialize = "CR")] CriuleniDistrict, #[strum(serialize = "CL")] - CalarasiDistrict, + CălărașiDistrict, #[strum(serialize = "CS")] - CauseniDistrict, + CăușeniDistrict, #[strum(serialize = "DO")] - DonduseniDistrict, + DondușeniDistrict, #[strum(serialize = "DR")] DrochiaDistrict, #[strum(serialize = "DU")] - DubasariDistrict, + DubăsariDistrict, #[strum(serialize = "ED")] - EdinetDistrict, + EdinețDistrict, #[strum(serialize = "FL")] - FlorestiDistrict, + FloreștiDistrict, #[strum(serialize = "FA")] - FalestiDistrict, + FăleștiDistrict, #[strum(serialize = "GA")] - Gagauzia, + Găgăuzia, #[strum(serialize = "GL")] GlodeniDistrict, #[strum(serialize = "HI")] - HincestiDistrict, + HînceștiDistrict, #[strum(serialize = "IA")] IaloveniDistrict, #[strum(serialize = "NI")] NisporeniDistrict, #[strum(serialize = "OC")] - OcnitaDistrict, + OcnițaDistrict, #[strum(serialize = "OR")] OrheiDistrict, #[strum(serialize = "RE")] RezinaDistrict, #[strum(serialize = "RI")] - RiscaniDistrict, + RîșcaniDistrict, #[strum(serialize = "SO")] SorocaDistrict, #[strum(serialize = "ST")] - StraseniDistrict, + StrășeniDistrict, #[strum(serialize = "SI")] - SingereiDistrict, + SîngereiDistrict, #[strum(serialize = "TA")] TaracliaDistrict, #[strum(serialize = "TE")] - TelenestiDistrict, + TeleneștiDistrict, #[strum(serialize = "SN")] TransnistriaAutonomousTerritorialUnit, #[strum(serialize = "UN")] UngheniDistrict, #[strum(serialize = "SD")] - SoldanestiDistrict, + ȘoldăneștiDistrict, #[strum(serialize = "SV")] - StefanVodaDistrict, + ȘtefanVodăDistrict, } #[derive( @@ -4049,11 +4195,11 @@ pub enum MontenegroStatesAbbreviation { #[strum(serialize = "14")] PljevljaMunicipality, #[strum(serialize = "15")] - PluzineMunicipality, + PlužineMunicipality, #[strum(serialize = "16")] PodgoricaMunicipality, #[strum(serialize = "17")] - RozajeMunicipality, + RožajeMunicipality, #[strum(serialize = "19")] TivatMunicipality, #[strum(serialize = "20")] @@ -4061,7 +4207,7 @@ pub enum MontenegroStatesAbbreviation { #[strum(serialize = "18")] SavnikMunicipality, #[strum(serialize = "21")] - ZabljakMunicipality, + ŽabljakMunicipality, } #[derive( @@ -4211,7 +4357,7 @@ pub enum NorthMacedoniaStatesAbbreviation { #[strum(serialize = "62")] PrilepMunicipality, #[strum(serialize = "63")] - ProbistipMunicipality, + ProbishtipMunicipality, #[strum(serialize = "64")] RadovisMunicipality, #[strum(serialize = "65")] @@ -4269,7 +4415,7 @@ pub enum NorthMacedoniaStatesAbbreviation { #[strum(serialize = "83")] StipMunicipality, #[strum(serialize = "84")] - SutoOrizariMunicipality, + ShutoOrizariMunicipality, #[strum(serialize = "30")] ZelinoMunicipality, } @@ -4326,40 +4472,38 @@ pub enum NorwayStatesAbbreviation { Debug, Clone, PartialEq, Eq, Serialize, Deserialize, strum::Display, strum::EnumString, )] pub enum PolandStatesAbbreviation { - #[strum(serialize = "WP")] - GreaterPolandVoivodeship, - #[strum(serialize = "KI")] - Kielce, - #[strum(serialize = "KP")] - KuyavianPomeranianVoivodeship, - #[strum(serialize = "MA")] - LesserPolandVoivodeship, - #[strum(serialize = "DS")] - LowerSilesianVoivodeship, - #[strum(serialize = "LU")] - LublinVoivodeship, - #[strum(serialize = "LB")] - LubuszVoivodeship, - #[strum(serialize = "MZ")] - MasovianVoivodeship, - #[strum(serialize = "OP")] - OpoleVoivodeship, - #[strum(serialize = "PK")] - PodkarpackieVoivodeship, - #[strum(serialize = "PD")] - PodlaskieVoivodeship, - #[strum(serialize = "PM")] - PomeranianVoivodeship, - #[strum(serialize = "SL")] - SilesianVoivodeship, - #[strum(serialize = "WN")] - WarmianMasurianVoivodeship, - #[strum(serialize = "ZP")] - WestPomeranianVoivodeship, - #[strum(serialize = "LD")] - LodzVoivodeship, - #[strum(serialize = "SK")] - SwietokrzyskieVoivodeship, + #[strum(serialize = "30")] + GreaterPoland, + #[strum(serialize = "26")] + HolyCross, + #[strum(serialize = "04")] + KuyaviaPomerania, + #[strum(serialize = "12")] + LesserPoland, + #[strum(serialize = "02")] + LowerSilesia, + #[strum(serialize = "06")] + Lublin, + #[strum(serialize = "08")] + Lubusz, + #[strum(serialize = "10")] + Łódź, + #[strum(serialize = "14")] + Mazovia, + #[strum(serialize = "20")] + Podlaskie, + #[strum(serialize = "22")] + Pomerania, + #[strum(serialize = "24")] + Silesia, + #[strum(serialize = "18")] + Subcarpathia, + #[strum(serialize = "16")] + UpperSilesia, + #[strum(serialize = "28")] + WarmiaMasuria, + #[strum(serialize = "32")] + WestPomerania, } #[derive( @@ -4603,23 +4747,33 @@ pub enum SwitzerlandStatesAbbreviation { )] pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "ABE")] - AberdeenCity, + Aberdeen, #[strum(serialize = "ABD")] Aberdeenshire, #[strum(serialize = "ANS")] Angus, + #[strum(serialize = "ANT")] + Antrim, #[strum(serialize = "ANN")] AntrimAndNewtownabbey, + #[strum(serialize = "ARD")] + Ards, #[strum(serialize = "AND")] ArdsAndNorthDown, #[strum(serialize = "AGB")] ArgyllAndBute, + #[strum(serialize = "ARM")] + ArmaghCityAndDistrictCouncil, #[strum(serialize = "ABC")] - ArmaghCityBanbridgeAndCraigavon, - #[strum(serialize = "BDG")] - BarkingAndDagenham, - #[strum(serialize = "BNE")] - Barnet, + ArmaghBanbridgeAndCraigavon, + #[strum(serialize = "SH-AC")] + AscensionIsland, + #[strum(serialize = "BLA")] + BallymenaBorough, + #[strum(serialize = "BLY")] + Ballymoney, + #[strum(serialize = "BNB")] + Banbridge, #[strum(serialize = "BNS")] Barnsley, #[strum(serialize = "BAS")] @@ -4627,9 +4781,7 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "BDF")] Bedford, #[strum(serialize = "BFS")] - BelfastCity, - #[strum(serialize = "BEX")] - Bexley, + BelfastDistrict, #[strum(serialize = "BIR")] Birmingham, #[strum(serialize = "BBD")] @@ -4637,41 +4789,35 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "BPL")] Blackpool, #[strum(serialize = "BGW")] - BlaenauGwent, + BlaenauGwentCountyBorough, #[strum(serialize = "BOL")] Bolton, - #[strum(serialize = "BCP")] - BournemouthChristchurchAndPoole, + #[strum(serialize = "BMH")] + Bournemouth, #[strum(serialize = "BRC")] BracknellForest, #[strum(serialize = "BRD")] Bradford, - #[strum(serialize = "BEN")] - Brent, #[strum(serialize = "BGE")] - Bridgend, + BridgendCountyBorough, #[strum(serialize = "BNH")] BrightonAndHove, - #[strum(serialize = "BST")] - BristolCityOf, - #[strum(serialize = "BRY")] - Bromley, #[strum(serialize = "BKM")] Buckinghamshire, #[strum(serialize = "BUR")] Bury, #[strum(serialize = "CAY")] - Caerphilly, + CaerphillyCountyBorough, #[strum(serialize = "CLD")] Calderdale, #[strum(serialize = "CAM")] Cambridgeshire, - #[strum(serialize = "CMD")] - Camden, - #[strum(serialize = "CRF")] - Cardiff, #[strum(serialize = "CMN")] Carmarthenshire, + #[strum(serialize = "CKF")] + CarrickfergusBoroughCouncil, + #[strum(serialize = "CSR")] + Castlereagh, #[strum(serialize = "CCG")] CausewayCoastAndGlens, #[strum(serialize = "CBF")] @@ -4682,44 +4828,84 @@ pub enum UnitedKingdomStatesAbbreviation { CheshireEast, #[strum(serialize = "CHW")] CheshireWestAndChester, + #[strum(serialize = "CRF")] + CityAndCountyOfCardiff, + #[strum(serialize = "SWA")] + CityAndCountyOfSwansea, + #[strum(serialize = "BST")] + CityOfBristol, + #[strum(serialize = "DER")] + CityOfDerby, + #[strum(serialize = "KHL")] + CityOfKingstonUponHull, + #[strum(serialize = "LCE")] + CityOfLeicester, + #[strum(serialize = "LND")] + CityOfLondon, + #[strum(serialize = "NGM")] + CityOfNottingham, + #[strum(serialize = "PTE")] + CityOfPeterborough, + #[strum(serialize = "PLY")] + CityOfPlymouth, + #[strum(serialize = "POR")] + CityOfPortsmouth, + #[strum(serialize = "STH")] + CityOfSouthampton, + #[strum(serialize = "STE")] + CityOfStokeOnTrent, + #[strum(serialize = "SND")] + CityOfSunderland, + #[strum(serialize = "WSM")] + CityOfWestminster, + #[strum(serialize = "WLV")] + CityOfWolverhampton, + #[strum(serialize = "YOR")] + CityOfYork, #[strum(serialize = "CLK")] Clackmannanshire, + #[strum(serialize = "CLR")] + ColeraineBoroughCouncil, #[strum(serialize = "CWY")] - Conwy, + ConwyCountyBorough, + #[strum(serialize = "CKT")] + CookstownDistrictCouncil, #[strum(serialize = "CON")] Cornwall, + #[strum(serialize = "DUR")] + CountyDurham, #[strum(serialize = "COV")] Coventry, - #[strum(serialize = "CRY")] - Croydon, + #[strum(serialize = "CGV")] + CraigavonBoroughCouncil, #[strum(serialize = "CMA")] Cumbria, #[strum(serialize = "DAL")] Darlington, #[strum(serialize = "DEN")] Denbighshire, - #[strum(serialize = "DER")] - Derby, #[strum(serialize = "DBY")] Derbyshire, #[strum(serialize = "DRS")] - DerryAndStrabane, + DerryCityAndStrabane, + #[strum(serialize = "DRY")] + DerryCityCouncil, #[strum(serialize = "DEV")] Devon, #[strum(serialize = "DNC")] Doncaster, #[strum(serialize = "DOR")] Dorset, + #[strum(serialize = "DOW")] + DownDistrictCouncil, #[strum(serialize = "DUD")] Dudley, #[strum(serialize = "DGY")] DumfriesAndGalloway, #[strum(serialize = "DND")] - DundeeCity, - #[strum(serialize = "DUR")] - DurhamCounty, - #[strum(serialize = "EAL")] - Ealing, + Dundee, + #[strum(serialize = "DGN")] + DungannonAndSouthTyroneBoroughCouncil, #[strum(serialize = "EAY")] EastAyrshire, #[strum(serialize = "EDU")] @@ -4733,17 +4919,17 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "ESX")] EastSussex, #[strum(serialize = "EDH")] - EdinburghCityOf, - #[strum(serialize = "ELS")] - EileanSiar, - #[strum(serialize = "ENF")] - Enfield, + Edinburgh, + #[strum(serialize = "ENG")] + England, #[strum(serialize = "ESS")] Essex, #[strum(serialize = "FAL")] Falkirk, #[strum(serialize = "FMO")] FermanaghAndOmagh, + #[strum(serialize = "FER")] + FermanaghDistrictCouncil, #[strum(serialize = "FIF")] Fife, #[strum(serialize = "FLN")] @@ -4751,91 +4937,119 @@ pub enum UnitedKingdomStatesAbbreviation { #[strum(serialize = "GAT")] Gateshead, #[strum(serialize = "GLG")] - GlasgowCity, + Glasgow, #[strum(serialize = "GLS")] Gloucestershire, - #[strum(serialize = "GRE")] - Greenwich, #[strum(serialize = "GWN")] Gwynedd, - #[strum(serialize = "HCK")] - Hackney, #[strum(serialize = "HAL")] Halton, - #[strum(serialize = "HMF")] - HammersmithAndFulham, #[strum(serialize = "HAM")] Hampshire, - #[strum(serialize = "HRY")] - Haringey, - #[strum(serialize = "HRW")] - Harrow, #[strum(serialize = "HPL")] Hartlepool, - #[strum(serialize = "HAV")] - Havering, #[strum(serialize = "HEF")] Herefordshire, #[strum(serialize = "HRT")] Hertfordshire, #[strum(serialize = "HLD")] Highland, - #[strum(serialize = "HIL")] - Hillingdon, - #[strum(serialize = "HNS")] - Hounslow, #[strum(serialize = "IVC")] Inverclyde, - #[strum(serialize = "AGY")] - IsleOfAnglesey, #[strum(serialize = "IOW")] IsleOfWight, #[strum(serialize = "IOS")] IslesOfScilly, - #[strum(serialize = "ISL")] - Islington, - #[strum(serialize = "KEC")] - KensingtonAndChelsea, #[strum(serialize = "KEN")] Kent, - #[strum(serialize = "KHL")] - KingstonUponHull, - #[strum(serialize = "KTT")] - KingstonUponThames, #[strum(serialize = "KIR")] Kirklees, #[strum(serialize = "KWL")] Knowsley, - #[strum(serialize = "LBH")] - Lambeth, #[strum(serialize = "LAN")] Lancashire, + #[strum(serialize = "LRN")] + LarneBoroughCouncil, #[strum(serialize = "LDS")] Leeds, - #[strum(serialize = "LCE")] - Leicester, #[strum(serialize = "LEC")] Leicestershire, - #[strum(serialize = "LEW")] - Lewisham, + #[strum(serialize = "LMV")] + LimavadyBoroughCouncil, #[strum(serialize = "LIN")] Lincolnshire, #[strum(serialize = "LBC")] LisburnAndCastlereagh, + #[strum(serialize = "LSB")] + LisburnCityCouncil, #[strum(serialize = "LIV")] Liverpool, - #[strum(serialize = "LND")] - LondonCityOf, - #[strum(serialize = "LUT")] - Luton, + #[strum(serialize = "BDG")] + LondonBoroughOfBarkingAndDagenham, + #[strum(serialize = "BNE")] + LondonBoroughOfBarnet, + #[strum(serialize = "BEX")] + LondonBoroughOfBexley, + #[strum(serialize = "BEN")] + LondonBoroughOfBrent, + #[strum(serialize = "BRY")] + LondonBoroughOfBromley, + #[strum(serialize = "CMD")] + LondonBoroughOfCamden, + #[strum(serialize = "CRY")] + LondonBoroughOfCroydon, + #[strum(serialize = "EAL")] + LondonBoroughOfEaling, + #[strum(serialize = "ENF")] + LondonBoroughOfEnfield, + #[strum(serialize = "HCK")] + LondonBoroughOfHackney, + #[strum(serialize = "HMF")] + LondonBoroughOfHammersmithAndFulham, + #[strum(serialize = "HRY")] + LondonBoroughOfHaringey, + #[strum(serialize = "HRW")] + LondonBoroughOfHarrow, + #[strum(serialize = "HAV")] + LondonBoroughOfHavering, + #[strum(serialize = "HIL")] + LondonBoroughOfHillingdon, + #[strum(serialize = "HNS")] + LondonBoroughOfHounslow, + #[strum(serialize = "ISL")] + LondonBoroughOfIslington, + #[strum(serialize = "LBH")] + LondonBoroughOfLambeth, + #[strum(serialize = "LEW")] + LondonBoroughOfLewisham, + #[strum(serialize = "MRT")] + LondonBoroughOfMerton, + #[strum(serialize = "NWM")] + LondonBoroughOfNewham, + #[strum(serialize = "RDB")] + LondonBoroughOfRedbridge, + #[strum(serialize = "RIC")] + LondonBoroughOfRichmondUponThames, + #[strum(serialize = "SWK")] + LondonBoroughOfSouthwark, + #[strum(serialize = "STN")] + LondonBoroughOfSutton, + #[strum(serialize = "TWH")] + LondonBoroughOfTowerHamlets, + #[strum(serialize = "WFT")] + LondonBoroughOfWalthamForest, + #[strum(serialize = "WND")] + LondonBoroughOfWandsworth, + #[strum(serialize = "MFT")] + MagherafeltDistrictCouncil, #[strum(serialize = "MAN")] Manchester, #[strum(serialize = "MDW")] Medway, #[strum(serialize = "MTY")] - MerthyrTydfil, - #[strum(serialize = "MRT")] - Merton, + MerthyrTydfilCountyBorough, + #[strum(serialize = "WGN")] + MetropolitanBoroughOfWigan, #[strum(serialize = "MEA")] MidAndEastAntrim, #[strum(serialize = "MUL")] @@ -4850,20 +5064,26 @@ pub enum UnitedKingdomStatesAbbreviation { Monmouthshire, #[strum(serialize = "MRY")] Moray, + #[strum(serialize = "MYL")] + MoyleDistrictCouncil, #[strum(serialize = "NTL")] - NeathPortTalbot, + NeathPortTalbotCountyBorough, #[strum(serialize = "NET")] NewcastleUponTyne, - #[strum(serialize = "NWM")] - Newham, #[strum(serialize = "NWP")] Newport, + #[strum(serialize = "NYM")] + NewryAndMourneDistrictCouncil, #[strum(serialize = "NMD")] NewryMourneAndDown, + #[strum(serialize = "NTA")] + NewtownabbeyBoroughCouncil, #[strum(serialize = "NFK")] Norfolk, #[strum(serialize = "NAY")] NorthAyrshire, + #[strum(serialize = "NDN")] + NorthDownBoroughCouncil, #[strum(serialize = "NEL")] NorthEastLincolnshire, #[strum(serialize = "NLK")] @@ -4878,52 +5098,58 @@ pub enum UnitedKingdomStatesAbbreviation { NorthYorkshire, #[strum(serialize = "NTH")] Northamptonshire, + #[strum(serialize = "NIR")] + NorthernIreland, #[strum(serialize = "NBL")] Northumberland, - #[strum(serialize = "NGM")] - Nottingham, #[strum(serialize = "NTT")] Nottinghamshire, #[strum(serialize = "OLD")] Oldham, + #[strum(serialize = "OMH")] + OmaghDistrictCouncil, #[strum(serialize = "ORK")] OrkneyIslands, + #[strum(serialize = "ELS")] + OuterHebrides, #[strum(serialize = "OXF")] Oxfordshire, #[strum(serialize = "PEM")] Pembrokeshire, #[strum(serialize = "PKN")] PerthAndKinross, - #[strum(serialize = "PTE")] - Peterborough, - #[strum(serialize = "PLY")] - Plymouth, - #[strum(serialize = "POR")] - Portsmouth, + #[strum(serialize = "POL")] + Poole, #[strum(serialize = "POW")] Powys, #[strum(serialize = "RDG")] Reading, - #[strum(serialize = "RDB")] - Redbridge, #[strum(serialize = "RCC")] RedcarAndCleveland, #[strum(serialize = "RFW")] Renfrewshire, #[strum(serialize = "RCT")] - RhonddaCynonTaff, - #[strum(serialize = "RIC")] - RichmondUponThames, + RhonddaCynonTaf, #[strum(serialize = "RCH")] Rochdale, #[strum(serialize = "ROT")] Rotherham, + #[strum(serialize = "GRE")] + RoyalBoroughOfGreenwich, + #[strum(serialize = "KEC")] + RoyalBoroughOfKensingtonAndChelsea, + #[strum(serialize = "KTT")] + RoyalBoroughOfKingstonUponThames, #[strum(serialize = "RUT")] Rutland, + #[strum(serialize = "SH-HL")] + SaintHelena, #[strum(serialize = "SLF")] Salford, #[strum(serialize = "SAW")] Sandwell, + #[strum(serialize = "SCT")] + Scotland, #[strum(serialize = "SCB")] ScottishBorders, #[strum(serialize = "SFT")] @@ -4948,12 +5174,8 @@ pub enum UnitedKingdomStatesAbbreviation { SouthLanarkshire, #[strum(serialize = "STY")] SouthTyneside, - #[strum(serialize = "STH")] - Southampton, #[strum(serialize = "SOS")] SouthendOnSea, - #[strum(serialize = "SWK")] - Southwark, #[strum(serialize = "SHN")] StHelens, #[strum(serialize = "STS")] @@ -4964,18 +5186,12 @@ pub enum UnitedKingdomStatesAbbreviation { Stockport, #[strum(serialize = "STT")] StocktonOnTees, - #[strum(serialize = "STE")] - StokeOnTrent, + #[strum(serialize = "STB")] + StrabaneDistrictCouncil, #[strum(serialize = "SFK")] Suffolk, - #[strum(serialize = "SND")] - Sunderland, #[strum(serialize = "SRY")] Surrey, - #[strum(serialize = "STN")] - Sutton, - #[strum(serialize = "SWA")] - Swansea, #[strum(serialize = "SWD")] Swindon, #[strum(serialize = "TAM")] @@ -4988,20 +5204,18 @@ pub enum UnitedKingdomStatesAbbreviation { Torbay, #[strum(serialize = "TOF")] Torfaen, - #[strum(serialize = "TWH")] - TowerHamlets, #[strum(serialize = "TRF")] Trafford, + #[strum(serialize = "UKM")] + UnitedKingdom, #[strum(serialize = "VGL")] ValeOfGlamorgan, #[strum(serialize = "WKF")] Wakefield, + #[strum(serialize = "WLS")] + Wales, #[strum(serialize = "WLL")] Walsall, - #[strum(serialize = "WFT")] - WalthamForest, - #[strum(serialize = "WND")] - Wandsworth, #[strum(serialize = "WRT")] Warrington, #[strum(serialize = "WAR")] @@ -5014,10 +5228,6 @@ pub enum UnitedKingdomStatesAbbreviation { WestLothian, #[strum(serialize = "WSX")] WestSussex, - #[strum(serialize = "WSM")] - Westminster, - #[strum(serialize = "WGN")] - Wigan, #[strum(serialize = "WIL")] Wiltshire, #[strum(serialize = "WNM")] @@ -5026,14 +5236,10 @@ pub enum UnitedKingdomStatesAbbreviation { Wirral, #[strum(serialize = "WOK")] Wokingham, - #[strum(serialize = "WLV")] - Wolverhampton, #[strum(serialize = "WOR")] Worcestershire, #[strum(serialize = "WRX")] - Wrexham, - #[strum(serialize = "YOR")] - York, + WrexhamCountyBorough, } #[derive( @@ -5517,6 +5723,320 @@ pub enum SloveniaStatesAbbreviation { Jesenice, #[strum(serialize = "163")] Jezersko, + #[strum(serialize = "042")] + Jursinci, + #[strum(serialize = "043")] + Kamnik, + #[strum(serialize = "044")] + KanalObSoci, + #[strum(serialize = "045")] + Kidricevo, + #[strum(serialize = "046")] + Kobarid, + #[strum(serialize = "047")] + Kobilje, + #[strum(serialize = "049")] + Komen, + #[strum(serialize = "164")] + Komenda, + #[strum(serialize = "050")] + Koper, + #[strum(serialize = "197")] + KostanjevicaNaKrki, + #[strum(serialize = "165")] + Kostel, + #[strum(serialize = "051")] + Kozje, + #[strum(serialize = "048")] + Kocevje, + #[strum(serialize = "052")] + Kranj, + #[strum(serialize = "053")] + KranjskaGora, + #[strum(serialize = "166")] + Krizevci, + #[strum(serialize = "055")] + Kungota, + #[strum(serialize = "056")] + Kuzma, + #[strum(serialize = "057")] + Lasko, + #[strum(serialize = "058")] + Lenart, + #[strum(serialize = "059")] + Lendava, + #[strum(serialize = "060")] + Litija, + #[strum(serialize = "061")] + Ljubljana, + #[strum(serialize = "062")] + Ljubno, + #[strum(serialize = "063")] + Ljutomer, + #[strum(serialize = "064")] + Logatec, + #[strum(serialize = "208")] + LogDragomer, + #[strum(serialize = "167")] + LovrencNaPohorju, + #[strum(serialize = "065")] + LoskaDolina, + #[strum(serialize = "066")] + LoskiPotok, + #[strum(serialize = "068")] + Lukovica, + #[strum(serialize = "067")] + Luče, + #[strum(serialize = "069")] + Majsperk, + #[strum(serialize = "198")] + Makole, + #[strum(serialize = "070")] + Maribor, + #[strum(serialize = "168")] + Markovci, + #[strum(serialize = "071")] + Medvode, + #[strum(serialize = "072")] + Menges, + #[strum(serialize = "073")] + Metlika, + #[strum(serialize = "074")] + Mezica, + #[strum(serialize = "169")] + MiklavzNaDravskemPolju, + #[strum(serialize = "075")] + MirenKostanjevica, + #[strum(serialize = "212")] + Mirna, + #[strum(serialize = "170")] + MirnaPec, + #[strum(serialize = "076")] + Mislinja, + #[strum(serialize = "199")] + MokronogTrebelno, + #[strum(serialize = "078")] + MoravskeToplice, + #[strum(serialize = "077")] + Moravce, + #[strum(serialize = "079")] + Mozirje, + #[strum(serialize = "195")] + Apače, + #[strum(serialize = "196")] + Cirkulane, + #[strum(serialize = "038")] + IlirskaBistrica, + #[strum(serialize = "054")] + Krsko, + #[strum(serialize = "123")] + Skofljica, + #[strum(serialize = "080")] + MurskaSobota, + #[strum(serialize = "081")] + Muta, + #[strum(serialize = "082")] + Naklo, + #[strum(serialize = "083")] + Nazarje, + #[strum(serialize = "084")] + NovaGorica, + #[strum(serialize = "086")] + Odranci, + #[strum(serialize = "171")] + Oplotnica, + #[strum(serialize = "087")] + Ormoz, + #[strum(serialize = "088")] + Osilnica, + #[strum(serialize = "089")] + Pesnica, + #[strum(serialize = "090")] + Piran, + #[strum(serialize = "091")] + Pivka, + #[strum(serialize = "172")] + Podlehnik, + #[strum(serialize = "093")] + Podvelka, + #[strum(serialize = "092")] + Podcetrtek, + #[strum(serialize = "200")] + Poljcane, + #[strum(serialize = "173")] + Polzela, + #[strum(serialize = "094")] + Postojna, + #[strum(serialize = "174")] + Prebold, + #[strum(serialize = "095")] + Preddvor, + #[strum(serialize = "175")] + Prevalje, + #[strum(serialize = "096")] + Ptuj, + #[strum(serialize = "097")] + Puconci, + #[strum(serialize = "100")] + Radenci, + #[strum(serialize = "099")] + Radece, + #[strum(serialize = "101")] + RadljeObDravi, + #[strum(serialize = "102")] + Radovljica, + #[strum(serialize = "103")] + RavneNaKoroskem, + #[strum(serialize = "176")] + Razkrizje, + #[strum(serialize = "098")] + RaceFram, + #[strum(serialize = "201")] + RenčeVogrsko, + #[strum(serialize = "209")] + RecicaObSavinji, + #[strum(serialize = "104")] + Ribnica, + #[strum(serialize = "177")] + RibnicaNaPohorju, + #[strum(serialize = "107")] + Rogatec, + #[strum(serialize = "106")] + RogaskaSlatina, + #[strum(serialize = "105")] + Rogasovci, + #[strum(serialize = "108")] + Ruse, + #[strum(serialize = "178")] + SelnicaObDravi, + #[strum(serialize = "109")] + Semic, + #[strum(serialize = "110")] + Sevnica, + #[strum(serialize = "111")] + Sezana, + #[strum(serialize = "112")] + SlovenjGradec, + #[strum(serialize = "113")] + SlovenskaBistrica, + #[strum(serialize = "114")] + SlovenskeKonjice, + #[strum(serialize = "179")] + Sodrazica, + #[strum(serialize = "180")] + Solcava, + #[strum(serialize = "202")] + SredisceObDravi, + #[strum(serialize = "115")] + Starse, + #[strum(serialize = "203")] + Straza, + #[strum(serialize = "181")] + SvetaAna, + #[strum(serialize = "204")] + SvetaTrojica, + #[strum(serialize = "182")] + SvetiAndraz, + #[strum(serialize = "116")] + SvetiJurijObScavnici, + #[strum(serialize = "210")] + SvetiJurijVSlovenskihGoricah, + #[strum(serialize = "205")] + SvetiTomaz, + #[strum(serialize = "184")] + Tabor, + #[strum(serialize = "010")] + Tišina, + #[strum(serialize = "128")] + Tolmin, + #[strum(serialize = "129")] + Trbovlje, + #[strum(serialize = "130")] + Trebnje, + #[strum(serialize = "185")] + TrnovskaVas, + #[strum(serialize = "186")] + Trzin, + #[strum(serialize = "131")] + Tržič, + #[strum(serialize = "132")] + Turnišče, + #[strum(serialize = "187")] + VelikaPolana, + #[strum(serialize = "134")] + VelikeLašče, + #[strum(serialize = "188")] + Veržej, + #[strum(serialize = "135")] + Videm, + #[strum(serialize = "136")] + Vipava, + #[strum(serialize = "137")] + Vitanje, + #[strum(serialize = "138")] + Vodice, + #[strum(serialize = "139")] + Vojnik, + #[strum(serialize = "189")] + Vransko, + #[strum(serialize = "140")] + Vrhnika, + #[strum(serialize = "141")] + Vuzenica, + #[strum(serialize = "142")] + ZagorjeObSavi, + #[strum(serialize = "143")] + Zavrč, + #[strum(serialize = "144")] + Zreče, + #[strum(serialize = "015")] + Črenšovci, + #[strum(serialize = "016")] + ČrnaNaKoroškem, + #[strum(serialize = "017")] + Črnomelj, + #[strum(serialize = "033")] + Šalovci, + #[strum(serialize = "183")] + ŠempeterVrtojba, + #[strum(serialize = "118")] + Šentilj, + #[strum(serialize = "119")] + Šentjernej, + #[strum(serialize = "120")] + Šentjur, + #[strum(serialize = "211")] + Šentrupert, + #[strum(serialize = "117")] + Šenčur, + #[strum(serialize = "121")] + Škocjan, + #[strum(serialize = "122")] + ŠkofjaLoka, + #[strum(serialize = "124")] + ŠmarjePriJelšah, + #[strum(serialize = "206")] + ŠmarješkeToplice, + #[strum(serialize = "125")] + ŠmartnoObPaki, + #[strum(serialize = "194")] + ŠmartnoPriLitiji, + #[strum(serialize = "126")] + Šoštanj, + #[strum(serialize = "127")] + Štore, + #[strum(serialize = "190")] + Žalec, + #[strum(serialize = "146")] + Železniki, + #[strum(serialize = "191")] + Žetale, + #[strum(serialize = "147")] + Žiri, + #[strum(serialize = "192")] + Žirovnica, + #[strum(serialize = "193")] + Žužemberk, } #[derive( diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index efb8eb86f34..05657216d9d 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -2465,36 +2465,31 @@ impl ForeignTryFrom for PolandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "PolandStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "PolandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "greater poland voivodeship" => Ok(Self::GreaterPolandVoivodeship), - "kielce" => Ok(Self::Kielce), - "kuyavian pomeranian voivodeship" => Ok(Self::KuyavianPomeranianVoivodeship), - "lesser poland voivodeship" => Ok(Self::LesserPolandVoivodeship), - "lower silesian voivodeship" => Ok(Self::LowerSilesianVoivodeship), - "lublin voivodeship" => Ok(Self::LublinVoivodeship), - "lubusz voivodeship" => Ok(Self::LubuszVoivodeship), - "masovian voivodeship" => Ok(Self::MasovianVoivodeship), - "opole voivodeship" => Ok(Self::OpoleVoivodeship), - "podkarpackie voivodeship" => Ok(Self::PodkarpackieVoivodeship), - "podlaskie voivodeship" => Ok(Self::PodlaskieVoivodeship), - "pomeranian voivodeship" => Ok(Self::PomeranianVoivodeship), - "silesian voivodeship" => Ok(Self::SilesianVoivodeship), - "warmian masurian voivodeship" => Ok(Self::WarmianMasurianVoivodeship), - "west pomeranian voivodeship" => Ok(Self::WestPomeranianVoivodeship), - "lodz voivodeship" => Ok(Self::LodzVoivodeship), - "swietokrzyskie voivodeship" => Ok(Self::SwietokrzyskieVoivodeship), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Greater Poland" => Ok(Self::GreaterPoland), + "Holy Cross" => Ok(Self::HolyCross), + "Kuyavia-Pomerania" => Ok(Self::KuyaviaPomerania), + "Lesser Poland" => Ok(Self::LesserPoland), + "Lower Silesia" => Ok(Self::LowerSilesia), + "Lublin" => Ok(Self::Lublin), + "Lubusz" => Ok(Self::Lubusz), + "Łódź" => Ok(Self::Łódź), + "Mazovia" => Ok(Self::Mazovia), + "Podlaskie" => Ok(Self::Podlaskie), + "Pomerania" => Ok(Self::Pomerania), + "Silesia" => Ok(Self::Silesia), + "Subcarpathia" => Ok(Self::Subcarpathia), + "Upper Silesia" => Ok(Self::UpperSilesia), + "Warmia-Masuria" => Ok(Self::WarmiaMasuria), + "West Pomerania" => Ok(Self::WestPomerania), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2503,49 +2498,138 @@ impl ForeignTryFrom for FranceStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "FranceStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "FranceStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "alo" => Ok(Self::Alo), - "alsace" => Ok(Self::Alsace), - "aquitaine" => Ok(Self::Aquitaine), - "auvergne" => Ok(Self::Auvergne), - "auvergne rhone alpes" => Ok(Self::AuvergneRhoneAlpes), - "bourgogne franche comte" => Ok(Self::BourgogneFrancheComte), - "brittany" => Ok(Self::Brittany), - "burgundy" => Ok(Self::Burgundy), - "centre val de loire" => Ok(Self::CentreValDeLoire), - "champagne ardenne" => Ok(Self::ChampagneArdenne), - "corsica" => Ok(Self::Corsica), - "franche comte" => Ok(Self::FrancheComte), - "french guiana" => Ok(Self::FrenchGuiana), - "french polynesia" => Ok(Self::FrenchPolynesia), - "grand est" => Ok(Self::GrandEst), - "guadeloupe" => Ok(Self::Guadeloupe), - "hauts de france" => Ok(Self::HautsDeFrance), - "ile de france" => Ok(Self::IleDeFrance), - "normandy" => Ok(Self::Normandy), - "nouvelle aquitaine" => Ok(Self::NouvelleAquitaine), - "occitania" => Ok(Self::Occitania), - "paris" => Ok(Self::Paris), - "pays de la loire" => Ok(Self::PaysDeLaLoire), - "provence alpes cote d azur" => Ok(Self::ProvenceAlpesCoteDAzur), - "reunion" => Ok(Self::Reunion), - "saint barthelemy" => Ok(Self::SaintBarthelemy), - "saint martin" => Ok(Self::SaintMartin), - "saint pierre and miquelon" => Ok(Self::SaintPierreAndMiquelon), - "upper normandy" => Ok(Self::UpperNormandy), - "wallis and futuna" => Ok(Self::WallisAndFutuna), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Ain" => Ok(Self::Ain), + "Aisne" => Ok(Self::Aisne), + "Allier" => Ok(Self::Allier), + "Alpes-de-Haute-Provence" => Ok(Self::AlpesDeHauteProvence), + "Alpes-Maritimes" => Ok(Self::AlpesMaritimes), + "Alsace" => Ok(Self::Alsace), + "Ardèche" => Ok(Self::Ardeche), + "Ardennes" => Ok(Self::Ardennes), + "Ariège" => Ok(Self::Ariege), + "Aube" => Ok(Self::Aube), + "Aude" => Ok(Self::Aude), + "Auvergne-Rhône-Alpes" => Ok(Self::AuvergneRhoneAlpes), + "Aveyron" => Ok(Self::Aveyron), + "Bas-Rhin" => Ok(Self::BasRhin), + "Bouches-du-Rhône" => Ok(Self::BouchesDuRhone), + "Bourgogne-Franche-Comté" => Ok(Self::BourgogneFrancheComte), + "Bretagne" => Ok(Self::Bretagne), + "Calvados" => Ok(Self::Calvados), + "Cantal" => Ok(Self::Cantal), + "Centre-Val de Loire" => Ok(Self::CentreValDeLoire), + "Charente" => Ok(Self::Charente), + "Charente-Maritime" => Ok(Self::CharenteMaritime), + "Cher" => Ok(Self::Cher), + "Clipperton" => Ok(Self::Clipperton), + "Corrèze" => Ok(Self::Correze), + "Corse" => Ok(Self::Corse), + "Corse-du-Sud" => Ok(Self::CorseDuSud), + "Côte-d'Or" => Ok(Self::CoteDor), + "Côtes-d'Armor" => Ok(Self::CotesDarmor), + "Creuse" => Ok(Self::Creuse), + "Deux-Sèvres" => Ok(Self::DeuxSevres), + "Dordogne" => Ok(Self::Dordogne), + "Doubs" => Ok(Self::Doubs), + "Drôme" => Ok(Self::Drome), + "Essonne" => Ok(Self::Essonne), + "Eure" => Ok(Self::Eure), + "Eure-et-Loir" => Ok(Self::EureEtLoir), + "Finistère" => Ok(Self::Finistere), + "French Guiana" => Ok(Self::FrenchGuiana), + "French Polynesia" => Ok(Self::FrenchPolynesia), + "French Southern and Antarctic Lands" => Ok(Self::FrenchSouthernAndAntarcticLands), + "Gard" => Ok(Self::Gard), + "Gers" => Ok(Self::Gers), + "Gironde" => Ok(Self::Gironde), + "Grand-Est" => Ok(Self::GrandEst), + "Guadeloupe" => Ok(Self::Guadeloupe), + "Haut-Rhin" => Ok(Self::HautRhin), + "Haute-Corse" => Ok(Self::HauteCorse), + "Haute-Garonne" => Ok(Self::HauteGaronne), + "Haute-Loire" => Ok(Self::HauteLoire), + "Haute-Marne" => Ok(Self::HauteMarne), + "Haute-Saône" => Ok(Self::HauteSaone), + "Haute-Savoie" => Ok(Self::HauteSavoie), + "Haute-Vienne" => Ok(Self::HauteVienne), + "Hautes-Alpes" => Ok(Self::HautesAlpes), + "Hautes-Pyrénées" => Ok(Self::HautesPyrenees), + "Hauts-de-France" => Ok(Self::HautsDeFrance), + "Hauts-de-Seine" => Ok(Self::HautsDeSeine), + "Hérault" => Ok(Self::Herault), + "Île-de-France" => Ok(Self::IleDeFrance), + "Ille-et-Vilaine" => Ok(Self::IlleEtVilaine), + "Indre" => Ok(Self::Indre), + "Indre-et-Loire" => Ok(Self::IndreEtLoire), + "Isère" => Ok(Self::Isere), + "Jura" => Ok(Self::Jura), + "La Réunion" => Ok(Self::LaReunion), + "Landes" => Ok(Self::Landes), + "Loir-et-Cher" => Ok(Self::LoirEtCher), + "Loire" => Ok(Self::Loire), + "Loire-Atlantique" => Ok(Self::LoireAtlantique), + "Loiret" => Ok(Self::Loiret), + "Lot" => Ok(Self::Lot), + "Lot-et-Garonne" => Ok(Self::LotEtGaronne), + "Lozère" => Ok(Self::Lozere), + "Maine-et-Loire" => Ok(Self::MaineEtLoire), + "Manche" => Ok(Self::Manche), + "Marne" => Ok(Self::Marne), + "Martinique" => Ok(Self::Martinique), + "Mayenne" => Ok(Self::Mayenne), + "Mayotte" => Ok(Self::Mayotte), + "Métropole de Lyon" => Ok(Self::MetropoleDeLyon), + "Meurthe-et-Moselle" => Ok(Self::MeurtheEtMoselle), + "Meuse" => Ok(Self::Meuse), + "Morbihan" => Ok(Self::Morbihan), + "Moselle" => Ok(Self::Moselle), + "Nièvre" => Ok(Self::Nievre), + "Nord" => Ok(Self::Nord), + "Normandie" => Ok(Self::Normandie), + "Nouvelle-Aquitaine" => Ok(Self::NouvelleAquitaine), + "Occitanie" => Ok(Self::Occitanie), + "Oise" => Ok(Self::Oise), + "Orne" => Ok(Self::Orne), + "Paris" => Ok(Self::Paris), + "Pas-de-Calais" => Ok(Self::PasDeCalais), + "Pays-de-la-Loire" => Ok(Self::PaysDeLaLoire), + "Provence-Alpes-Côte-d'Azur" => Ok(Self::ProvenceAlpesCoteDazur), + "Puy-de-Dôme" => Ok(Self::PuyDeDome), + "Pyrénées-Atlantiques" => Ok(Self::PyreneesAtlantiques), + "Pyrénées-Orientales" => Ok(Self::PyreneesOrientales), + "Rhône" => Ok(Self::Rhone), + "Saint Pierre and Miquelon" => Ok(Self::SaintPierreAndMiquelon), + "Saint-Barthélemy" => Ok(Self::SaintBarthelemy), + "Saint-Martin" => Ok(Self::SaintMartin), + "Saône-et-Loire" => Ok(Self::SaoneEtLoire), + "Sarthe" => Ok(Self::Sarthe), + "Savoie" => Ok(Self::Savoie), + "Seine-et-Marne" => Ok(Self::SeineEtMarne), + "Seine-Maritime" => Ok(Self::SeineMaritime), + "Seine-Saint-Denis" => Ok(Self::SeineSaintDenis), + "Somme" => Ok(Self::Somme), + "Tarn" => Ok(Self::Tarn), + "Tarn-et-Garonne" => Ok(Self::TarnEtGaronne), + "Territoire de Belfort" => Ok(Self::TerritoireDeBelfort), + "Val-d'Oise" => Ok(Self::ValDoise), + "Val-de-Marne" => Ok(Self::ValDeMarne), + "Var" => Ok(Self::Var), + "Vaucluse" => Ok(Self::Vaucluse), + "Vendée" => Ok(Self::Vendee), + "Vienne" => Ok(Self::Vienne), + "Vosges" => Ok(Self::Vosges), + "Wallis and Futuna" => Ok(Self::WallisAndFutuna), + "Yonne" => Ok(Self::Yonne), + "Yvelines" => Ok(Self::Yvelines), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2553,38 +2637,32 @@ impl ForeignTryFrom for FranceStatesAbbreviation { impl ForeignTryFrom for GermanyStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "GermanyStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "GermanyStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "baden wurttemberg" => Ok(Self::BadenWurttemberg), - "bavaria" => Ok(Self::Bavaria), - "berlin" => Ok(Self::Berlin), - "brandenburg" => Ok(Self::Brandenburg), - "bremen" => Ok(Self::Bremen), - "hamburg" => Ok(Self::Hamburg), - "hesse" => Ok(Self::Hesse), - "lower saxony" => Ok(Self::LowerSaxony), - "mecklenburg vorpommern" => Ok(Self::MecklenburgVorpommern), - "north rhine westphalia" => Ok(Self::NorthRhineWestphalia), - "rhineland palatinate" => Ok(Self::RhinelandPalatinate), - "saarland" => Ok(Self::Saarland), - "saxony" => Ok(Self::Saxony), - "saxony anhalt" => Ok(Self::SaxonyAnhalt), - "schleswig holstein" => Ok(Self::SchleswigHolstein), - "thuringia" => Ok(Self::Thuringia), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Baden-Württemberg" => Ok(Self::BW), + "Bavaria" => Ok(Self::BY), + "Berlin" => Ok(Self::BE), + "Brandenburg" => Ok(Self::BB), + "Bremen" => Ok(Self::HB), + "Hamburg" => Ok(Self::HH), + "Hessen" => Ok(Self::HE), + "Lower Saxony" => Ok(Self::NI), + "Mecklenburg-Vorpommern" => Ok(Self::MV), + "North Rhine-Westphalia" => Ok(Self::NW), + "Rhineland-Palatinate" => Ok(Self::RP), + "Saarland" => Ok(Self::SL), + "Saxony" => Ok(Self::SN), + "Saxony-Anhalt" => Ok(Self::ST), + "Schleswig-Holstein" => Ok(Self::SH), + "Thuringia" => Ok(Self::TH), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2593,83 +2671,79 @@ impl ForeignTryFrom for SpainStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "SpainStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "SpainStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "a coruna province" => Ok(Self::ACorunaProvince), - "albacete province" => Ok(Self::AlbaceteProvince), - "alicante province" => Ok(Self::AlicanteProvince), - "almeria province" => Ok(Self::AlmeriaProvince), - "andalusia" => Ok(Self::Andalusia), - "araba alava" => Ok(Self::ArabaAlava), - "aragon" => Ok(Self::Aragon), - "badajoz province" => Ok(Self::BadajozProvince), - "balearic islands" => Ok(Self::BalearicIslands), - "barcelona province" => Ok(Self::BarcelonaProvince), - "basque country" => Ok(Self::BasqueCountry), - "biscay" => Ok(Self::Biscay), - "burgos province" => Ok(Self::BurgosProvince), - "canary islands" => Ok(Self::CanaryIslands), - "cantabria" => Ok(Self::Cantabria), - "castellon province" => Ok(Self::CastellonProvince), - "castile and leon" => Ok(Self::CastileAndLeon), - "castile la mancha" => Ok(Self::CastileLaMancha), - "catalonia" => Ok(Self::Catalonia), - "ceuta" => Ok(Self::Ceuta), - "ciudad real province" => Ok(Self::CiudadRealProvince), - "community of madrid" => Ok(Self::CommunityOfMadrid), - "cuenca province" => Ok(Self::CuencaProvince), - "caceres province" => Ok(Self::CaceresProvince), - "cadiz province" => Ok(Self::CadizProvince), - "cordoba province" => Ok(Self::CordobaProvince), - "extremadura" => Ok(Self::Extremadura), - "galicia" => Ok(Self::Galicia), - "gipuzkoa" => Ok(Self::Gipuzkoa), - "girona province" => Ok(Self::GironaProvince), - "granada province" => Ok(Self::GranadaProvince), - "guadalajara province" => Ok(Self::GuadalajaraProvince), - "huelva province" => Ok(Self::HuelvaProvince), - "huesca province" => Ok(Self::HuescaProvince), - "jaen province" => Ok(Self::JaenProvince), - "la rioja" => Ok(Self::LaRioja), - "las palmas province" => Ok(Self::LasPalmasProvince), - "leon province" => Ok(Self::LeonProvince), - "lleida province" => Ok(Self::LleidaProvince), - "lugo province" => Ok(Self::LugoProvince), - "madrid province" => Ok(Self::MadridProvince), - "melilla" => Ok(Self::Melilla), - "murcia province" => Ok(Self::MurciaProvince), - "malaga province" => Ok(Self::MalagaProvince), - "navarre" => Ok(Self::Navarre), - "ourense province" => Ok(Self::OurenseProvince), - "palencia province" => Ok(Self::PalenciaProvince), - "pontevedra province" => Ok(Self::PontevedraProvince), - "province of asturias" => Ok(Self::ProvinceOfAsturias), - "province of avila" => Ok(Self::ProvinceOfAvila), - "region of murcia" => Ok(Self::RegionOfMurcia), - "salamanca province" => Ok(Self::SalamancaProvince), - "santa cruz de tenerife province" => Ok(Self::SantaCruzDeTenerifeProvince), - "segovia province" => Ok(Self::SegoviaProvince), - "seville province" => Ok(Self::SevilleProvince), - "soria province" => Ok(Self::SoriaProvince), - "tarragona province" => Ok(Self::TarragonaProvince), - "teruel province" => Ok(Self::TeruelProvince), - "toledo province" => Ok(Self::ToledoProvince), - "valencia province" => Ok(Self::ValenciaProvince), - "valencian community" => Ok(Self::ValencianCommunity), - "valladolid province" => Ok(Self::ValladolidProvince), - "zamora province" => Ok(Self::ZamoraProvince), - "zaragoza province" => Ok(Self::ZaragozaProvince), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "A Coruña Province" => Ok(Self::ACorunaProvince), + "Albacete Province" => Ok(Self::AlbaceteProvince), + "Alicante Province" => Ok(Self::AlicanteProvince), + "Almería Province" => Ok(Self::AlmeriaProvince), + "Andalusia" => Ok(Self::Andalusia), + "Araba / Álava" => Ok(Self::ArabaAlava), + "Aragon" => Ok(Self::Aragon), + "Badajoz Province" => Ok(Self::BadajozProvince), + "Balearic Islands" => Ok(Self::BalearicIslands), + "Barcelona Province" => Ok(Self::BarcelonaProvince), + "Basque Country" => Ok(Self::BasqueCountry), + "Biscay" => Ok(Self::Biscay), + "Burgos Province" => Ok(Self::BurgosProvince), + "Canary Islands" => Ok(Self::CanaryIslands), + "Cantabria" => Ok(Self::Cantabria), + "Castellón Province" => Ok(Self::CastellonProvince), + "Castile and León" => Ok(Self::CastileAndLeon), + "Castilla-La Mancha" => Ok(Self::CastileLaMancha), + "Catalonia" => Ok(Self::Catalonia), + "Ceuta" => Ok(Self::Ceuta), + "Ciudad Real Province" => Ok(Self::CiudadRealProvince), + "Community of Madrid" => Ok(Self::CommunityOfMadrid), + "Cuenca Province" => Ok(Self::CuencaProvince), + "Cáceres Province" => Ok(Self::CaceresProvince), + "Cádiz Province" => Ok(Self::CadizProvince), + "Córdoba Province" => Ok(Self::CordobaProvince), + "Extremadura" => Ok(Self::Extremadura), + "Galicia" => Ok(Self::Galicia), + "Gipuzkoa" => Ok(Self::Gipuzkoa), + "Girona Province" => Ok(Self::GironaProvince), + "Granada Province" => Ok(Self::GranadaProvince), + "Guadalajara Province" => Ok(Self::GuadalajaraProvince), + "Huelva Province" => Ok(Self::HuelvaProvince), + "Huesca Province" => Ok(Self::HuescaProvince), + "Jaén Province" => Ok(Self::JaenProvince), + "La Rioja" => Ok(Self::LaRioja), + "Las Palmas Province" => Ok(Self::LasPalmasProvince), + "León Province" => Ok(Self::LeonProvince), + "Lleida Province" => Ok(Self::LleidaProvince), + "Lugo Province" => Ok(Self::LugoProvince), + "Madrid Province" => Ok(Self::MadridProvince), + "Melilla" => Ok(Self::Melilla), + "Murcia Province" => Ok(Self::MurciaProvince), + "Málaga Province" => Ok(Self::MalagaProvince), + "Navarre" => Ok(Self::Navarre), + "Ourense Province" => Ok(Self::OurenseProvince), + "Palencia Province" => Ok(Self::PalenciaProvince), + "Pontevedra Province" => Ok(Self::PontevedraProvince), + "Province of Asturias" => Ok(Self::ProvinceOfAsturias), + "Province of Ávila" => Ok(Self::ProvinceOfAvila), + "Region of Murcia" => Ok(Self::RegionOfMurcia), + "Salamanca Province" => Ok(Self::SalamancaProvince), + "Santa Cruz de Tenerife Province" => Ok(Self::SantaCruzDeTenerifeProvince), + "Segovia Province" => Ok(Self::SegoviaProvince), + "Seville Province" => Ok(Self::SevilleProvince), + "Soria Province" => Ok(Self::SoriaProvince), + "Tarragona Province" => Ok(Self::TarragonaProvince), + "Teruel Province" => Ok(Self::TeruelProvince), + "Toledo Province" => Ok(Self::ToledoProvince), + "Valencia Province" => Ok(Self::ValenciaProvince), + "Valencian Community" => Ok(Self::ValencianCommunity), + "Valladolid Province" => Ok(Self::ValladolidProvince), + "Zamora Province" => Ok(Self::ZamoraProvince), + "Zaragoza Province" => Ok(Self::ZaragozaProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2678,60 +2752,56 @@ impl ForeignTryFrom for ItalyStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "ItalyStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "ItalyStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "abruzzo" => Ok(Self::Abruzzo), - "aosta valley" => Ok(Self::AostaValley), - "apulia" => Ok(Self::Apulia), - "basilicata" => Ok(Self::Basilicata), - "benevento province" => Ok(Self::BeneventoProvince), - "calabria" => Ok(Self::Calabria), - "campania" => Ok(Self::Campania), - "emilia romagna" => Ok(Self::EmiliaRomagna), - "friuli venezia giulia" => Ok(Self::FriuliVeneziaGiulia), - "lazio" => Ok(Self::Lazio), - "liguria" => Ok(Self::Liguria), - "lombardy" => Ok(Self::Lombardy), - "marche" => Ok(Self::Marche), - "molise" => Ok(Self::Molise), - "piedmont" => Ok(Self::Piedmont), - "sardinia" => Ok(Self::Sardinia), - "sicily" => Ok(Self::Sicily), - "trentino south tyrol" => Ok(Self::TrentinoSouthTyrol), - "tuscany" => Ok(Self::Tuscany), - "umbria" => Ok(Self::Umbria), - "veneto" => Ok(Self::Veneto), - "agrigento" => Ok(Self::Agrigento), - "caltanissetta" => Ok(Self::Caltanissetta), - "enna" => Ok(Self::Enna), - "ragusa" => Ok(Self::Ragusa), - "siracusa" => Ok(Self::Siracusa), - "trapani" => Ok(Self::Trapani), - "bari" => Ok(Self::Bari), - "bologna" => Ok(Self::Bologna), - "cagliari" => Ok(Self::Cagliari), - "catania" => Ok(Self::Catania), - "florence" => Ok(Self::Florence), - "genoa" => Ok(Self::Genoa), - "messina" => Ok(Self::Messina), - "milan" => Ok(Self::Milan), - "naples" => Ok(Self::Naples), - "palermo" => Ok(Self::Palermo), - "reggio calabria" => Ok(Self::ReggioCalabria), - "rome" => Ok(Self::Rome), - "turin" => Ok(Self::Turin), - "venice" => Ok(Self::Venice), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Abruzzo" => Ok(Self::Abruzzo), + "Aosta Valley" => Ok(Self::AostaValley), + "Apulia" => Ok(Self::Apulia), + "Basilicata" => Ok(Self::Basilicata), + "Benevento Province" => Ok(Self::BeneventoProvince), + "Calabria" => Ok(Self::Calabria), + "Campania" => Ok(Self::Campania), + "Emilia-Romagna" => Ok(Self::EmiliaRomagna), + "Friuli–Venezia Giulia" => Ok(Self::FriuliVeneziaGiulia), + "Lazio" => Ok(Self::Lazio), + "Liguria" => Ok(Self::Liguria), + "Lombardy" => Ok(Self::Lombardy), + "Marche" => Ok(Self::Marche), + "Molise" => Ok(Self::Molise), + "Piedmont" => Ok(Self::Piedmont), + "Sardinia" => Ok(Self::Sardinia), + "Sicily" => Ok(Self::Sicily), + "Trentino-South Tyrol" => Ok(Self::TrentinoSouthTyrol), + "Tuscany" => Ok(Self::Tuscany), + "Umbria" => Ok(Self::Umbria), + "Veneto" => Ok(Self::Veneto), + "Libero consorzio comunale di Agrigento" => Ok(Self::Agrigento), + "Libero consorzio comunale di Caltanissetta" => Ok(Self::Caltanissetta), + "Libero consorzio comunale di Enna" => Ok(Self::Enna), + "Libero consorzio comunale di Ragusa" => Ok(Self::Ragusa), + "Libero consorzio comunale di Siracusa" => Ok(Self::Siracusa), + "Libero consorzio comunale di Trapani" => Ok(Self::Trapani), + "Metropolitan City of Bari" => Ok(Self::Bari), + "Metropolitan City of Bologna" => Ok(Self::Bologna), + "Metropolitan City of Cagliari" => Ok(Self::Cagliari), + "Metropolitan City of Catania" => Ok(Self::Catania), + "Metropolitan City of Florence" => Ok(Self::Florence), + "Metropolitan City of Genoa" => Ok(Self::Genoa), + "Metropolitan City of Messina" => Ok(Self::Messina), + "Metropolitan City of Milan" => Ok(Self::Milan), + "Metropolitan City of Naples" => Ok(Self::Naples), + "Metropolitan City of Palermo" => Ok(Self::Palermo), + "Metropolitan City of Reggio Calabria" => Ok(Self::ReggioCalabria), + "Metropolitan City of Rome" => Ok(Self::Rome), + "Metropolitan City of Turin" => Ok(Self::Turin), + "Metropolitan City of Venice" => Ok(Self::Venice), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2740,39 +2810,36 @@ impl ForeignTryFrom for NorwayStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "NorwayStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "NorwayStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "akershus" => Ok(Self::Akershus), - "buskerud" => Ok(Self::Buskerud), - "finnmark" => Ok(Self::Finnmark), - "hedmark" => Ok(Self::Hedmark), - "hordaland" => Ok(Self::Hordaland), - "janmayen" => Ok(Self::JanMayen), - "nordtrondelag" => Ok(Self::NordTrondelag), - "nordland" => Ok(Self::Nordland), - "oppland" => Ok(Self::Oppland), - "oslo" => Ok(Self::Oslo), - "rogaland" => Ok(Self::Rogaland), - "sognogfjordane" => Ok(Self::SognOgFjordane), - "svalbard" => Ok(Self::Svalbard), - "sortrondelag" => Ok(Self::SorTrondelag), - "telemark" => Ok(Self::Telemark), - "troms" => Ok(Self::Troms), - "trondelag" => Ok(Self::Trondelag), - "vestagder" => Ok(Self::VestAgder), - "vestfold" => Ok(Self::Vestfold), - "ostfold" => Ok(Self::Ostfold), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Akershus" => Ok(Self::Akershus), + "Buskerud" => Ok(Self::Buskerud), + "Finnmark" => Ok(Self::Finnmark), + "Hedmark" => Ok(Self::Hedmark), + "Hordaland" => Ok(Self::Hordaland), + "Jan Mayen" => Ok(Self::JanMayen), + "Møre og Romsdal" => Ok(Self::MoreOgRomsdal), + "Nord-Trøndelag" => Ok(Self::NordTrondelag), + "Nordland" => Ok(Self::Nordland), + "Oppland" => Ok(Self::Oppland), + "Oslo" => Ok(Self::Oslo), + "Rogaland" => Ok(Self::Rogaland), + "Sogn og Fjordane" => Ok(Self::SognOgFjordane), + "Svalbard" => Ok(Self::Svalbard), + "Sør-Trøndelag" => Ok(Self::SorTrondelag), + "Telemark" => Ok(Self::Telemark), + "Troms" => Ok(Self::Troms), + "Trøndelag" => Ok(Self::Trondelag), + "Vest-Agder" => Ok(Self::VestAgder), + "Vestfold" => Ok(Self::Vestfold), + "Østfold" => Ok(Self::Ostfold), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2780,34 +2847,28 @@ impl ForeignTryFrom for NorwayStatesAbbreviation { impl ForeignTryFrom for AlbaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AlbaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AlbaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "berat" => Ok(Self::Berat), - "diber" => Ok(Self::Diber), - "durres" => Ok(Self::Durres), - "elbasan" => Ok(Self::Elbasan), - "fier" => Ok(Self::Fier), - "gjirokaster" => Ok(Self::Gjirokaster), - "korce" => Ok(Self::Korce), - "kukes" => Ok(Self::Kukes), - "lezhe" => Ok(Self::Lezhe), - "shkoder" => Ok(Self::Shkoder), - "tirane" => Ok(Self::Tirane), - "vlore" => Ok(Self::Vlore), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Berat" => Ok(Self::Berat), + "Dibër" => Ok(Self::Diber), + "Durrës" => Ok(Self::Durres), + "Elbasan" => Ok(Self::Elbasan), + "Fier" => Ok(Self::Fier), + "Gjirokastër" => Ok(Self::Gjirokaster), + "Korçë" => Ok(Self::Korce), + "Kukës" => Ok(Self::Kukes), + "Lezhë" => Ok(Self::Lezhe), + "Shkodër" => Ok(Self::Shkoder), + "Tiranë" => Ok(Self::Tirane), + "Vlorë" => Ok(Self::Vlore), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2815,29 +2876,23 @@ impl ForeignTryFrom for AlbaniaStatesAbbreviation { impl ForeignTryFrom for AndorraStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AndorraStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AndorraStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "andorra la vella" => Ok(Self::AndorraLaVella), - "canillo" => Ok(Self::Canillo), - "encamp" => Ok(Self::Encamp), - "escaldes engordany" => Ok(Self::EscaldesEngordany), - "la massana" => Ok(Self::LaMassana), - "ordino" => Ok(Self::Ordino), - "sant julia de loria" => Ok(Self::SantJuliaDeLoria), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Andorra la Vella" => Ok(Self::AndorraLaVella), + "Canillo" => Ok(Self::Canillo), + "Encamp" => Ok(Self::Encamp), + "Escaldes-Engordany" => Ok(Self::EscaldesEngordany), + "La Massana" => Ok(Self::LaMassana), + "Ordino" => Ok(Self::Ordino), + "Sant Julià de Lòria" => Ok(Self::SantJuliaDeLoria), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2845,31 +2900,25 @@ impl ForeignTryFrom for AndorraStatesAbbreviation { impl ForeignTryFrom for AustriaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "AustriaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "AustriaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "burgenland" => Ok(Self::Burgenland), - "carinthia" => Ok(Self::Carinthia), - "lower austria" => Ok(Self::LowerAustria), - "salzburg" => Ok(Self::Salzburg), - "styria" => Ok(Self::Styria), - "tyrol" => Ok(Self::Tyrol), - "upper austria" => Ok(Self::UpperAustria), - "vienna" => Ok(Self::Vienna), - "vorarlberg" => Ok(Self::Vorarlberg), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Burgenland" => Ok(Self::Burgenland), + "Carinthia" => Ok(Self::Carinthia), + "Lower Austria" => Ok(Self::LowerAustria), + "Salzburg" => Ok(Self::Salzburg), + "Styria" => Ok(Self::Styria), + "Tyrol" => Ok(Self::Tyrol), + "Upper Austria" => Ok(Self::UpperAustria), + "Vienna" => Ok(Self::Vienna), + "Vorarlberg" => Ok(Self::Vorarlberg), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2877,63 +2926,57 @@ impl ForeignTryFrom for AustriaStatesAbbreviation { impl ForeignTryFrom for RomaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "RomaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "RomaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "alba" => Ok(Self::Alba), - "arad county" => Ok(Self::AradCounty), - "arges" => Ok(Self::Arges), - "bacau county" => Ok(Self::BacauCounty), - "bihor county" => Ok(Self::BihorCounty), - "bistrita nasaud county" => Ok(Self::BistritaNasaudCounty), - "botosani county" => Ok(Self::BotosaniCounty), - "braila" => Ok(Self::Braila), - "brasov county" => Ok(Self::BrasovCounty), - "bucharest" => Ok(Self::Bucharest), - "buzau county" => Ok(Self::BuzauCounty), - "caras severin county" => Ok(Self::CarasSeverinCounty), - "cluj county" => Ok(Self::ClujCounty), - "constanta county" => Ok(Self::ConstantaCounty), - "covasna county" => Ok(Self::CovasnaCounty), - "calarasi county" => Ok(Self::CalarasiCounty), - "dolj county" => Ok(Self::DoljCounty), - "dambovita county" => Ok(Self::DambovitaCounty), - "galati county" => Ok(Self::GalatiCounty), - "giurgiu county" => Ok(Self::GiurgiuCounty), - "gorj county" => Ok(Self::GorjCounty), - "harghita county" => Ok(Self::HarghitaCounty), - "hunedoara county" => Ok(Self::HunedoaraCounty), - "ialomita county" => Ok(Self::IalomitaCounty), - "iasi county" => Ok(Self::IasiCounty), - "ilfov county" => Ok(Self::IlfovCounty), - "mehedinti county" => Ok(Self::MehedintiCounty), - "mures county" => Ok(Self::MuresCounty), - "neamt county" => Ok(Self::NeamtCounty), - "olt county" => Ok(Self::OltCounty), - "prahova county" => Ok(Self::PrahovaCounty), - "satu mare county" => Ok(Self::SatuMareCounty), - "sibiu county" => Ok(Self::SibiuCounty), - "suceava county" => Ok(Self::SuceavaCounty), - "salaj county" => Ok(Self::SalajCounty), - "teleorman county" => Ok(Self::TeleormanCounty), - "timis county" => Ok(Self::TimisCounty), - "tulcea county" => Ok(Self::TulceaCounty), - "vaslui county" => Ok(Self::VasluiCounty), - "vrancea county" => Ok(Self::VranceaCounty), - "valcea county" => Ok(Self::ValceaCounty), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Alba" => Ok(Self::Alba), + "Arad County" => Ok(Self::AradCounty), + "Argeș" => Ok(Self::Arges), + "Bacău County" => Ok(Self::BacauCounty), + "Bihor County" => Ok(Self::BihorCounty), + "Bistrița-Năsăud County" => Ok(Self::BistritaNasaudCounty), + "Botoșani County" => Ok(Self::BotosaniCounty), + "Brăila" => Ok(Self::Braila), + "Brașov County" => Ok(Self::BrasovCounty), + "Bucharest" => Ok(Self::Bucharest), + "Buzău County" => Ok(Self::BuzauCounty), + "Caraș-Severin County" => Ok(Self::CarasSeverinCounty), + "Cluj County" => Ok(Self::ClujCounty), + "Constanța County" => Ok(Self::ConstantaCounty), + "Covasna County" => Ok(Self::CovasnaCounty), + "Călărași County" => Ok(Self::CalarasiCounty), + "Dolj County" => Ok(Self::DoljCounty), + "Dâmbovița County" => Ok(Self::DambovitaCounty), + "Galați County" => Ok(Self::GalatiCounty), + "Giurgiu County" => Ok(Self::GiurgiuCounty), + "Gorj County" => Ok(Self::GorjCounty), + "Harghita County" => Ok(Self::HarghitaCounty), + "Hunedoara County" => Ok(Self::HunedoaraCounty), + "Ialomița County" => Ok(Self::IalomitaCounty), + "Iași County" => Ok(Self::IasiCounty), + "Ilfov County" => Ok(Self::IlfovCounty), + "Mehedinți County" => Ok(Self::MehedintiCounty), + "Mureș County" => Ok(Self::MuresCounty), + "Neamț County" => Ok(Self::NeamtCounty), + "Olt County" => Ok(Self::OltCounty), + "Prahova County" => Ok(Self::PrahovaCounty), + "Satu Mare County" => Ok(Self::SatuMareCounty), + "Sibiu County" => Ok(Self::SibiuCounty), + "Suceava County" => Ok(Self::SuceavaCounty), + "Sălaj County" => Ok(Self::SalajCounty), + "Teleorman County" => Ok(Self::TeleormanCounty), + "Timiș County" => Ok(Self::TimisCounty), + "Tulcea County" => Ok(Self::TulceaCounty), + "Vaslui County" => Ok(Self::VasluiCounty), + "Vrancea County" => Ok(Self::VranceaCounty), + "Vâlcea County" => Ok(Self::ValceaCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2941,42 +2984,36 @@ impl ForeignTryFrom for RomaniaStatesAbbreviation { impl ForeignTryFrom for PortugalStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "PortugalStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "PortugalStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aveiro district" => Ok(Self::AveiroDistrict), - "azores" => Ok(Self::Azores), - "beja district" => Ok(Self::BejaDistrict), - "braga district" => Ok(Self::BragaDistrict), - "braganca district" => Ok(Self::BragancaDistrict), - "castelo branco district" => Ok(Self::CasteloBrancoDistrict), - "coimbra district" => Ok(Self::CoimbraDistrict), - "faro district" => Ok(Self::FaroDistrict), - "guarda district" => Ok(Self::GuardaDistrict), - "leiria district" => Ok(Self::LeiriaDistrict), - "lisbon district" => Ok(Self::LisbonDistrict), - "madeira" => Ok(Self::Madeira), - "portalegre district" => Ok(Self::PortalegreDistrict), - "porto district" => Ok(Self::PortoDistrict), - "santarem district" => Ok(Self::SantaremDistrict), - "setubal district" => Ok(Self::SetubalDistrict), - "viana do castelo district" => Ok(Self::VianaDoCasteloDistrict), - "vila real district" => Ok(Self::VilaRealDistrict), - "viseu district" => Ok(Self::ViseuDistrict), - "evora district" => Ok(Self::EvoraDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aveiro District" => Ok(Self::AveiroDistrict), + "Azores" => Ok(Self::Azores), + "Beja District" => Ok(Self::BejaDistrict), + "Braga District" => Ok(Self::BragaDistrict), + "Bragança District" => Ok(Self::BragancaDistrict), + "Castelo Branco District" => Ok(Self::CasteloBrancoDistrict), + "Coimbra District" => Ok(Self::CoimbraDistrict), + "Faro District" => Ok(Self::FaroDistrict), + "Guarda District" => Ok(Self::GuardaDistrict), + "Leiria District" => Ok(Self::LeiriaDistrict), + "Lisbon District" => Ok(Self::LisbonDistrict), + "Madeira" => Ok(Self::Madeira), + "Portalegre District" => Ok(Self::PortalegreDistrict), + "Porto District" => Ok(Self::PortoDistrict), + "Santarém District" => Ok(Self::SantaremDistrict), + "Setúbal District" => Ok(Self::SetubalDistrict), + "Viana do Castelo District" => Ok(Self::VianaDoCasteloDistrict), + "Vila Real District" => Ok(Self::VilaRealDistrict), + "Viseu District" => Ok(Self::ViseuDistrict), + "Évora District" => Ok(Self::EvoraDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -2984,47 +3021,41 @@ impl ForeignTryFrom for PortugalStatesAbbreviation { impl ForeignTryFrom for SwitzerlandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "SwitzerlandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SwitzerlandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aargau" => Ok(Self::Aargau), - "appenzell ausserrhoden" => Ok(Self::AppenzellAusserrhoden), - "appenzell innerrhoden" => Ok(Self::AppenzellInnerrhoden), - "basel landschaft" => Ok(Self::BaselLandschaft), - "canton of fribourg" => Ok(Self::CantonOfFribourg), - "canton of geneva" => Ok(Self::CantonOfGeneva), - "canton of jura" => Ok(Self::CantonOfJura), - "canton of lucerne" => Ok(Self::CantonOfLucerne), - "canton of neuchatel" => Ok(Self::CantonOfNeuchatel), - "canton of schaffhausen" => Ok(Self::CantonOfSchaffhausen), - "canton of solothurn" => Ok(Self::CantonOfSolothurn), - "canton of st gallen" => Ok(Self::CantonOfStGallen), - "canton of valais" => Ok(Self::CantonOfValais), - "canton of vaud" => Ok(Self::CantonOfVaud), - "canton of zug" => Ok(Self::CantonOfZug), - "glarus" => Ok(Self::Glarus), - "graubunden" => Ok(Self::Graubunden), - "nidwalden" => Ok(Self::Nidwalden), - "obwalden" => Ok(Self::Obwalden), - "schwyz" => Ok(Self::Schwyz), - "thurgau" => Ok(Self::Thurgau), - "ticino" => Ok(Self::Ticino), - "uri" => Ok(Self::Uri), - "canton of bern" => Ok(Self::CantonOfBern), - "canton of zurich" => Ok(Self::CantonOfZurich), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aargau" => Ok(Self::Aargau), + "Appenzell Ausserrhoden" => Ok(Self::AppenzellAusserrhoden), + "Appenzell Innerrhoden" => Ok(Self::AppenzellInnerrhoden), + "Basel-Landschaft" => Ok(Self::BaselLandschaft), + "Canton of Fribourg" => Ok(Self::CantonOfFribourg), + "Canton of Geneva" => Ok(Self::CantonOfGeneva), + "Canton of Jura" => Ok(Self::CantonOfJura), + "Canton of Lucerne" => Ok(Self::CantonOfLucerne), + "Canton of Neuchâtel" => Ok(Self::CantonOfNeuchatel), + "Canton of Schaffhausen" => Ok(Self::CantonOfSchaffhausen), + "Canton of Solothurn" => Ok(Self::CantonOfSolothurn), + "Canton of St. Gallen" => Ok(Self::CantonOfStGallen), + "Canton of Valais" => Ok(Self::CantonOfValais), + "Canton of Vaud" => Ok(Self::CantonOfVaud), + "Canton of Zug" => Ok(Self::CantonOfZug), + "Glarus" => Ok(Self::Glarus), + "Graubünden" => Ok(Self::Graubunden), + "Nidwalden" => Ok(Self::Nidwalden), + "Obwalden" => Ok(Self::Obwalden), + "Schwyz" => Ok(Self::Schwyz), + "Thurgau" => Ok(Self::Thurgau), + "Ticino" => Ok(Self::Ticino), + "Uri" => Ok(Self::Uri), + "canton of Bern" => Ok(Self::CantonOfBern), + "canton of Zürich" => Ok(Self::CantonOfZurich), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3032,54 +3063,100 @@ impl ForeignTryFrom for SwitzerlandStatesAbbreviation { impl ForeignTryFrom for NorthMacedoniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "NorthMacedoniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "NorthMacedoniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aerodrom municipality" => Ok(Self::AerodromMunicipality), - "aracinovo municipality" => Ok(Self::AracinovoMunicipality), - "berovo municipality" => Ok(Self::BerovoMunicipality), - "bitola municipality" => Ok(Self::BitolaMunicipality), - "bogdanci municipality" => Ok(Self::BogdanciMunicipality), - "bogovinje municipality" => Ok(Self::BogovinjeMunicipality), - "bosilovo municipality" => Ok(Self::BosilovoMunicipality), - "brvenica municipality" => Ok(Self::BrvenicaMunicipality), - "butel municipality" => Ok(Self::ButelMunicipality), - "centar municipality" => Ok(Self::CentarMunicipality), - "centar zupa municipality" => Ok(Self::CentarZupaMunicipality), - "debarca municipality" => Ok(Self::DebarcaMunicipality), - "delcevo municipality" => Ok(Self::DelcevoMunicipality), - "demir hisar municipality" => Ok(Self::DemirHisarMunicipality), - "demir kapija municipality" => Ok(Self::DemirKapijaMunicipality), - "dojran municipality" => Ok(Self::DojranMunicipality), - "dolneni municipality" => Ok(Self::DolneniMunicipality), - "drugovo municipality" => Ok(Self::DrugovoMunicipality), - "gazi baba municipality" => Ok(Self::GaziBabaMunicipality), - "gevgelija municipality" => Ok(Self::GevgelijaMunicipality), - "gjorce petrov municipality" => Ok(Self::GjorcePetrovMunicipality), - "gostivar municipality" => Ok(Self::GostivarMunicipality), - "gradsko municipality" => Ok(Self::GradskoMunicipality), - "greater skopje" => Ok(Self::GreaterSkopje), - "ilinden municipality" => Ok(Self::IlindenMunicipality), - "jegunovce municipality" => Ok(Self::JegunovceMunicipality), - "karbinci" => Ok(Self::Karbinci), - "karpos municipality" => Ok(Self::KarposMunicipality), - "kavadarci municipality" => Ok(Self::KavadarciMunicipality), - "kisela voda municipality" => Ok(Self::KiselaVodaMunicipality), - "kicevo municipality" => Ok(Self::KicevoMunicipality), - "konce municipality" => Ok(Self::KonceMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aerodrom Municipality" => Ok(Self::AerodromMunicipality), + "Aračinovo Municipality" => Ok(Self::AracinovoMunicipality), + "Berovo Municipality" => Ok(Self::BerovoMunicipality), + "Bitola Municipality" => Ok(Self::BitolaMunicipality), + "Bogdanci Municipality" => Ok(Self::BogdanciMunicipality), + "Bogovinje Municipality" => Ok(Self::BogovinjeMunicipality), + "Bosilovo Municipality" => Ok(Self::BosilovoMunicipality), + "Brvenica Municipality" => Ok(Self::BrvenicaMunicipality), + "Butel Municipality" => Ok(Self::ButelMunicipality), + "Centar Municipality" => Ok(Self::CentarMunicipality), + "Centar Župa Municipality" => Ok(Self::CentarZupaMunicipality), + "Debarca Municipality" => Ok(Self::DebarcaMunicipality), + "Delčevo Municipality" => Ok(Self::DelcevoMunicipality), + "Demir Hisar Municipality" => Ok(Self::DemirHisarMunicipality), + "Demir Kapija Municipality" => Ok(Self::DemirKapijaMunicipality), + "Dojran Municipality" => Ok(Self::DojranMunicipality), + "Dolneni Municipality" => Ok(Self::DolneniMunicipality), + "Drugovo Municipality" => Ok(Self::DrugovoMunicipality), + "Gazi Baba Municipality" => Ok(Self::GaziBabaMunicipality), + "Gevgelija Municipality" => Ok(Self::GevgelijaMunicipality), + "Gjorče Petrov Municipality" => Ok(Self::GjorcePetrovMunicipality), + "Gostivar Municipality" => Ok(Self::GostivarMunicipality), + "Gradsko Municipality" => Ok(Self::GradskoMunicipality), + "Greater Skopje" => Ok(Self::GreaterSkopje), + "Ilinden Municipality" => Ok(Self::IlindenMunicipality), + "Jegunovce Municipality" => Ok(Self::JegunovceMunicipality), + "Karbinci" => Ok(Self::Karbinci), + "Karpoš Municipality" => Ok(Self::KarposMunicipality), + "Kavadarci Municipality" => Ok(Self::KavadarciMunicipality), + "Kisela Voda Municipality" => Ok(Self::KiselaVodaMunicipality), + "Kičevo Municipality" => Ok(Self::KicevoMunicipality), + "Konče Municipality" => Ok(Self::KonceMunicipality), + "Kočani Municipality" => Ok(Self::KocaniMunicipality), + "Kratovo Municipality" => Ok(Self::KratovoMunicipality), + "Kriva Palanka Municipality" => Ok(Self::KrivaPalankaMunicipality), + "Krivogaštani Municipality" => Ok(Self::KrivogastaniMunicipality), + "Kruševo Municipality" => Ok(Self::KrusevoMunicipality), + "Kumanovo Municipality" => Ok(Self::KumanovoMunicipality), + "Lipkovo Municipality" => Ok(Self::LipkovoMunicipality), + "Lozovo Municipality" => Ok(Self::LozovoMunicipality), + "Makedonska Kamenica Municipality" => Ok(Self::MakedonskaKamenicaMunicipality), + "Makedonski Brod Municipality" => Ok(Self::MakedonskiBrodMunicipality), + "Mavrovo and Rostuša Municipality" => Ok(Self::MavrovoAndRostusaMunicipality), + "Mogila Municipality" => Ok(Self::MogilaMunicipality), + "Negotino Municipality" => Ok(Self::NegotinoMunicipality), + "Novaci Municipality" => Ok(Self::NovaciMunicipality), + "Novo Selo Municipality" => Ok(Self::NovoSeloMunicipality), + "Ohrid Municipality" => Ok(Self::OhridMunicipality), + "Oslomej Municipality" => Ok(Self::OslomejMunicipality), + "Pehčevo Municipality" => Ok(Self::PehcevoMunicipality), + "Petrovec Municipality" => Ok(Self::PetrovecMunicipality), + "Plasnica Municipality" => Ok(Self::PlasnicaMunicipality), + "Prilep Municipality" => Ok(Self::PrilepMunicipality), + "Probištip Municipality" => Ok(Self::ProbishtipMunicipality), + "Radoviš Municipality" => Ok(Self::RadovisMunicipality), + "Rankovce Municipality" => Ok(Self::RankovceMunicipality), + "Resen Municipality" => Ok(Self::ResenMunicipality), + "Rosoman Municipality" => Ok(Self::RosomanMunicipality), + "Saraj Municipality" => Ok(Self::SarajMunicipality), + "Sopište Municipality" => Ok(Self::SopisteMunicipality), + "Staro Nagoričane Municipality" => Ok(Self::StaroNagoricaneMunicipality), + "Struga Municipality" => Ok(Self::StrugaMunicipality), + "Strumica Municipality" => Ok(Self::StrumicaMunicipality), + "Studeničani Municipality" => Ok(Self::StudenicaniMunicipality), + "Sveti Nikole Municipality" => Ok(Self::SvetiNikoleMunicipality), + "Tearce Municipality" => Ok(Self::TearceMunicipality), + "Tetovo Municipality" => Ok(Self::TetovoMunicipality), + "Valandovo Municipality" => Ok(Self::ValandovoMunicipality), + "Vasilevo Municipality" => Ok(Self::VasilevoMunicipality), + "Veles Municipality" => Ok(Self::VelesMunicipality), + "Vevčani Municipality" => Ok(Self::VevcaniMunicipality), + "Vinica Municipality" => Ok(Self::VinicaMunicipality), + "Vraneštica Municipality" => Ok(Self::VranesticaMunicipality), + "Vrapčište Municipality" => Ok(Self::VrapcisteMunicipality), + "Zajas Municipality" => Ok(Self::ZajasMunicipality), + "Zelenikovo Municipality" => Ok(Self::ZelenikovoMunicipality), + "Zrnovci Municipality" => Ok(Self::ZrnovciMunicipality), + "Čair Municipality" => Ok(Self::CairMunicipality), + "Čaška Municipality" => Ok(Self::CaskaMunicipality), + "Češinovo-Obleševo Municipality" => Ok(Self::CesinovoOblesevoMunicipality), + "Čučer-Sandevo Municipality" => Ok(Self::CucerSandevoMunicipality), + "Štip Municipality" => Ok(Self::StipMunicipality), + "Šuto Orizari Municipality" => Ok(Self::ShutoOrizariMunicipality), + "Želino Municipality" => Ok(Self::ZelinoMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3087,44 +3164,36 @@ impl ForeignTryFrom for NorthMacedoniaStatesAbbreviation { impl ForeignTryFrom for MontenegroStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "MontenegroStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "MontenegroStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "andrijevica municipality" => Ok(Self::AndrijevicaMunicipality), - "bar municipality" => Ok(Self::BarMunicipality), - "berane municipality" => Ok(Self::BeraneMunicipality), - "bijelo polje municipality" => Ok(Self::BijeloPoljeMunicipality), - "budva municipality" => Ok(Self::BudvaMunicipality), - "danilovgrad municipality" => Ok(Self::DanilovgradMunicipality), - "gusinje municipality" => Ok(Self::GusinjeMunicipality), - "kolasin municipality" => Ok(Self::KolasinMunicipality), - "kotor municipality" => Ok(Self::KotorMunicipality), - "mojkovac municipality" => Ok(Self::MojkovacMunicipality), - "niksic municipality" => Ok(Self::NiksicMunicipality), - "old royal capital cetinje" => Ok(Self::OldRoyalCapitalCetinje), - "petnjica municipality" => Ok(Self::PetnjicaMunicipality), - "plav municipality" => Ok(Self::PlavMunicipality), - "pljevlja municipality" => Ok(Self::PljevljaMunicipality), - "pluzine municipality" => Ok(Self::PluzineMunicipality), - "podgorica municipality" => Ok(Self::PodgoricaMunicipality), - "rozaje municipality" => Ok(Self::RozajeMunicipality), - "tivat municipality" => Ok(Self::TivatMunicipality), - "ulcinj municipality" => Ok(Self::UlcinjMunicipality), - "savnik municipality" => Ok(Self::SavnikMunicipality), - "zabljak municipality" => Ok(Self::ZabljakMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Andrijevica Municipality" => Ok(Self::AndrijevicaMunicipality), + "Bar Municipality" => Ok(Self::BarMunicipality), + "Berane Municipality" => Ok(Self::BeraneMunicipality), + "Bijelo Polje Municipality" => Ok(Self::BijeloPoljeMunicipality), + "Budva Municipality" => Ok(Self::BudvaMunicipality), + "Danilovgrad Municipality" => Ok(Self::DanilovgradMunicipality), + "Gusinje Municipality" => Ok(Self::GusinjeMunicipality), + "Kolašin Municipality" => Ok(Self::KolasinMunicipality), + "Kotor Municipality" => Ok(Self::KotorMunicipality), + "Mojkovac Municipality" => Ok(Self::MojkovacMunicipality), + "Nikšić Municipality" => Ok(Self::NiksicMunicipality), + "Petnjica Municipality" => Ok(Self::PetnjicaMunicipality), + "Plav Municipality" => Ok(Self::PlavMunicipality), + "Pljevlja Municipality" => Ok(Self::PljevljaMunicipality), + "Plužine Municipality" => Ok(Self::PlužineMunicipality), + "Podgorica Municipality" => Ok(Self::PodgoricaMunicipality), + "Rožaje Municipality" => Ok(Self::RožajeMunicipality), + "Tivat Municipality" => Ok(Self::TivatMunicipality), + "Ulcinj Municipality" => Ok(Self::UlcinjMunicipality), + "Žabljak Municipality" => Ok(Self::ŽabljakMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3133,20 +3202,16 @@ impl ForeignTryFrom for MonacoStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "MonacoStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "MonacoStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "monaco" => Ok(Self::Monaco), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Monaco" => Ok(Self::Monaco), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3154,37 +3219,31 @@ impl ForeignTryFrom for MonacoStatesAbbreviation { impl ForeignTryFrom for NetherlandsStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "NetherlandsStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "NetherlandsStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bonaire" => Ok(Self::Bonaire), - "drenthe" => Ok(Self::Drenthe), - "flevoland" => Ok(Self::Flevoland), - "friesland" => Ok(Self::Friesland), - "gelderland" => Ok(Self::Gelderland), - "groningen" => Ok(Self::Groningen), - "limburg" => Ok(Self::Limburg), - "north brabant" => Ok(Self::NorthBrabant), - "north holland" => Ok(Self::NorthHolland), - "overijssel" => Ok(Self::Overijssel), - "saba" => Ok(Self::Saba), - "sint eustatius" => Ok(Self::SintEustatius), - "south holland" => Ok(Self::SouthHolland), - "utrecht" => Ok(Self::Utrecht), - "zeeland" => Ok(Self::Zeeland), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bonaire" => Ok(Self::Bonaire), + "Drenthe" => Ok(Self::Drenthe), + "Flevoland" => Ok(Self::Flevoland), + "Friesland" => Ok(Self::Friesland), + "Gelderland" => Ok(Self::Gelderland), + "Groningen" => Ok(Self::Groningen), + "Limburg" => Ok(Self::Limburg), + "North Brabant" => Ok(Self::NorthBrabant), + "North Holland" => Ok(Self::NorthHolland), + "Overijssel" => Ok(Self::Overijssel), + "Saba" => Ok(Self::Saba), + "Sint Eustatius" => Ok(Self::SintEustatius), + "South Holland" => Ok(Self::SouthHolland), + "Utrecht" => Ok(Self::Utrecht), + "Zeeland" => Ok(Self::Zeeland), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3192,60 +3251,54 @@ impl ForeignTryFrom for NetherlandsStatesAbbreviation { impl ForeignTryFrom for MoldovaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "MoldovaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "MoldovaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "anenii noi district" => Ok(Self::AneniiNoiDistrict), - "basarabeasca district" => Ok(Self::BasarabeascaDistrict), - "bender municipality" => Ok(Self::BenderMunicipality), - "briceni district" => Ok(Self::BriceniDistrict), - "balti municipality" => Ok(Self::BaltiMunicipality), - "cahul district" => Ok(Self::CahulDistrict), - "cantemir district" => Ok(Self::CantemirDistrict), - "chisinau municipality" => Ok(Self::ChisinauMunicipality), - "cimislia district" => Ok(Self::CimisliaDistrict), - "criuleni district" => Ok(Self::CriuleniDistrict), - "calarasi district" => Ok(Self::CalarasiDistrict), - "causeni district" => Ok(Self::CauseniDistrict), - "donduseni district" => Ok(Self::DonduseniDistrict), - "drochia district" => Ok(Self::DrochiaDistrict), - "dubasari district" => Ok(Self::DubasariDistrict), - "edinet district" => Ok(Self::EdinetDistrict), - "floresti district" => Ok(Self::FlorestiDistrict), - "falesti district" => Ok(Self::FalestiDistrict), - "gagauzia" => Ok(Self::Gagauzia), - "glodeni district" => Ok(Self::GlodeniDistrict), - "hincesti district" => Ok(Self::HincestiDistrict), - "ialoveni district" => Ok(Self::IaloveniDistrict), - "nisporeni district" => Ok(Self::NisporeniDistrict), - "ocnita district" => Ok(Self::OcnitaDistrict), - "orhei district" => Ok(Self::OrheiDistrict), - "rezina district" => Ok(Self::RezinaDistrict), - "riscani district" => Ok(Self::RiscaniDistrict), - "soroca district" => Ok(Self::SorocaDistrict), - "straseni district" => Ok(Self::StraseniDistrict), - "singerei district" => Ok(Self::SingereiDistrict), - "taraclia district" => Ok(Self::TaracliaDistrict), - "telenesti district" => Ok(Self::TelenestiDistrict), - "transnistria autonomous territorial unit" => { - Ok(Self::TransnistriaAutonomousTerritorialUnit) - } - "ungheni district" => Ok(Self::UngheniDistrict), - "soldanesti district" => Ok(Self::SoldanestiDistrict), - "stefan voda district" => Ok(Self::StefanVodaDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Anenii Noi District" => Ok(Self::AneniiNoiDistrict), + "Basarabeasca District" => Ok(Self::BasarabeascaDistrict), + "Bender Municipality" => Ok(Self::BenderMunicipality), + "Briceni District" => Ok(Self::BriceniDistrict), + "Bălți Municipality" => Ok(Self::BălțiMunicipality), + "Cahul District" => Ok(Self::CahulDistrict), + "Cantemir District" => Ok(Self::CantemirDistrict), + "Chișinău Municipality" => Ok(Self::ChișinăuMunicipality), + "Cimișlia District" => Ok(Self::CimișliaDistrict), + "Criuleni District" => Ok(Self::CriuleniDistrict), + "Călărași District" => Ok(Self::CălărașiDistrict), + "Căușeni District" => Ok(Self::CăușeniDistrict), + "Dondușeni District" => Ok(Self::DondușeniDistrict), + "Drochia District" => Ok(Self::DrochiaDistrict), + "Dubăsari District" => Ok(Self::DubăsariDistrict), + "Edineț District" => Ok(Self::EdinețDistrict), + "Florești District" => Ok(Self::FloreștiDistrict), + "Fălești District" => Ok(Self::FăleștiDistrict), + "Găgăuzia" => Ok(Self::Găgăuzia), + "Glodeni District" => Ok(Self::GlodeniDistrict), + "Hîncești District" => Ok(Self::HînceștiDistrict), + "Ialoveni District" => Ok(Self::IaloveniDistrict), + "Nisporeni District" => Ok(Self::NisporeniDistrict), + "Ocnița District" => Ok(Self::OcnițaDistrict), + "Orhei District" => Ok(Self::OrheiDistrict), + "Rezina District" => Ok(Self::RezinaDistrict), + "Rîșcani District" => Ok(Self::RîșcaniDistrict), + "Soroca District" => Ok(Self::SorocaDistrict), + "Strășeni District" => Ok(Self::StrășeniDistrict), + "Sîngerei District" => Ok(Self::SîngereiDistrict), + "Taraclia District" => Ok(Self::TaracliaDistrict), + "Telenești District" => Ok(Self::TeleneștiDistrict), + "Transnistria Autonomous Territorial Unit" => { + Ok(Self::TransnistriaAutonomousTerritorialUnit) } - } + "Ungheni District" => Ok(Self::UngheniDistrict), + "Șoldănești District" => Ok(Self::ȘoldăneștiDistrict), + "Ștefan Vodă District" => Ok(Self::ȘtefanVodăDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -3253,99 +3306,85 @@ impl ForeignTryFrom for MoldovaStatesAbbreviation { impl ForeignTryFrom for LithuaniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "LithuaniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LithuaniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "akmene district municipality" => Ok(Self::AkmeneDistrictMunicipality), - "alytus city municipality" => Ok(Self::AlytusCityMunicipality), - "alytus county" => Ok(Self::AlytusCounty), - "alytus district municipality" => Ok(Self::AlytusDistrictMunicipality), - "birstonas municipality" => Ok(Self::BirstonasMunicipality), - "birzai district municipality" => Ok(Self::BirzaiDistrictMunicipality), - "druskininkai municipality" => Ok(Self::DruskininkaiMunicipality), - "elektrenai municipality" => Ok(Self::ElektrenaiMunicipality), - "ignalina district municipality" => Ok(Self::IgnalinaDistrictMunicipality), - "jonava district municipality" => Ok(Self::JonavaDistrictMunicipality), - "joniskis district municipality" => Ok(Self::JoniskisDistrictMunicipality), - "jurbarkas district municipality" => Ok(Self::JurbarkasDistrictMunicipality), - "kaisiadorys district municipality" => { - Ok(Self::KaisiadorysDistrictMunicipality) - } - "kalvarija municipality" => Ok(Self::KalvarijaMunicipality), - "kaunas city municipality" => Ok(Self::KaunasCityMunicipality), - "kaunas county" => Ok(Self::KaunasCounty), - "kaunas district municipality" => Ok(Self::KaunasDistrictMunicipality), - "kazlu ruda municipality" => Ok(Self::KazluRudaMunicipality), - "kelme district municipality" => Ok(Self::KelmeDistrictMunicipality), - "klaipeda city municipality" => Ok(Self::KlaipedaCityMunicipality), - "klaipeda county" => Ok(Self::KlaipedaCounty), - "klaipeda district municipality" => Ok(Self::KlaipedaDistrictMunicipality), - "kretinga district municipality" => Ok(Self::KretingaDistrictMunicipality), - "kupiskis district municipality" => Ok(Self::KupiskisDistrictMunicipality), - "kedainiai district municipality" => Ok(Self::KedainiaiDistrictMunicipality), - "lazdijai district municipality" => Ok(Self::LazdijaiDistrictMunicipality), - "marijampole county" => Ok(Self::MarijampoleCounty), - "marijampole municipality" => Ok(Self::MarijampoleMunicipality), - "mazeikiai district municipality" => Ok(Self::MazeikiaiDistrictMunicipality), - "moletai district municipality" => Ok(Self::MoletaiDistrictMunicipality), - "neringa municipality" => Ok(Self::NeringaMunicipality), - "pagegiai municipality" => Ok(Self::PagegiaiMunicipality), - "pakruojis district municipality" => Ok(Self::PakruojisDistrictMunicipality), - "palanga city municipality" => Ok(Self::PalangaCityMunicipality), - "panevezys city municipality" => Ok(Self::PanevezysCityMunicipality), - "panevezys county" => Ok(Self::PanevezysCounty), - "panevezys district municipality" => Ok(Self::PanevezysDistrictMunicipality), - "pasvalys district municipality" => Ok(Self::PasvalysDistrictMunicipality), - "plunge district municipality" => Ok(Self::PlungeDistrictMunicipality), - "prienai district municipality" => Ok(Self::PrienaiDistrictMunicipality), - "radviliskis district municipality" => { - Ok(Self::RadviliskisDistrictMunicipality) - } - "raseiniai district municipality" => Ok(Self::RaseiniaiDistrictMunicipality), - "rietavas municipality" => Ok(Self::RietavasMunicipality), - "rokiskis district municipality" => Ok(Self::RokiskisDistrictMunicipality), - "skuodas district municipality" => Ok(Self::SkuodasDistrictMunicipality), - "taurage county" => Ok(Self::TaurageCounty), - "taurage district municipality" => Ok(Self::TaurageDistrictMunicipality), - "telsiai county" => Ok(Self::TelsiaiCounty), - "telsiai district municipality" => Ok(Self::TelsiaiDistrictMunicipality), - "trakai district municipality" => Ok(Self::TrakaiDistrictMunicipality), - "ukmerge district municipality" => Ok(Self::UkmergeDistrictMunicipality), - "utena county" => Ok(Self::UtenaCounty), - "utena district municipality" => Ok(Self::UtenaDistrictMunicipality), - "varena district municipality" => Ok(Self::VarenaDistrictMunicipality), - "vilkaviskis district municipality" => { - Ok(Self::VilkaviskisDistrictMunicipality) - } - "vilnius city municipality" => Ok(Self::VilniusCityMunicipality), - "vilnius county" => Ok(Self::VilniusCounty), - "vilnius district municipality" => Ok(Self::VilniusDistrictMunicipality), - "visaginas municipality" => Ok(Self::VisaginasMunicipality), - "zarasai district municipality" => Ok(Self::ZarasaiDistrictMunicipality), - "sakiai district municipality" => Ok(Self::SakiaiDistrictMunicipality), - "salcininkai district municipality" => { - Ok(Self::SalcininkaiDistrictMunicipality) - } - "siauliai city municipality" => Ok(Self::SiauliaiCityMunicipality), - "siauliai county" => Ok(Self::SiauliaiCounty), - "siauliai district municipality" => Ok(Self::SiauliaiDistrictMunicipality), - "silale district municipality" => Ok(Self::SilaleDistrictMunicipality), - "silute district municipality" => Ok(Self::SiluteDistrictMunicipality), - "sirvintos district municipality" => Ok(Self::SirvintosDistrictMunicipality), - "svencionys district municipality" => Ok(Self::SvencionysDistrictMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Akmenė District Municipality" => Ok(Self::AkmeneDistrictMunicipality), + "Alytus City Municipality" => Ok(Self::AlytusCityMunicipality), + "Alytus County" => Ok(Self::AlytusCounty), + "Alytus District Municipality" => Ok(Self::AlytusDistrictMunicipality), + "Birštonas Municipality" => Ok(Self::BirstonasMunicipality), + "Biržai District Municipality" => Ok(Self::BirzaiDistrictMunicipality), + "Druskininkai municipality" => Ok(Self::DruskininkaiMunicipality), + "Elektrėnai municipality" => Ok(Self::ElektrenaiMunicipality), + "Ignalina District Municipality" => Ok(Self::IgnalinaDistrictMunicipality), + "Jonava District Municipality" => Ok(Self::JonavaDistrictMunicipality), + "Joniškis District Municipality" => Ok(Self::JoniskisDistrictMunicipality), + "Jurbarkas District Municipality" => Ok(Self::JurbarkasDistrictMunicipality), + "Kaišiadorys District Municipality" => Ok(Self::KaisiadorysDistrictMunicipality), + "Kalvarija municipality" => Ok(Self::KalvarijaMunicipality), + "Kaunas City Municipality" => Ok(Self::KaunasCityMunicipality), + "Kaunas County" => Ok(Self::KaunasCounty), + "Kaunas District Municipality" => Ok(Self::KaunasDistrictMunicipality), + "Kazlų Rūda municipality" => Ok(Self::KazluRudaMunicipality), + "Kelmė District Municipality" => Ok(Self::KelmeDistrictMunicipality), + "Klaipeda City Municipality" => Ok(Self::KlaipedaCityMunicipality), + "Klaipėda County" => Ok(Self::KlaipedaCounty), + "Klaipėda District Municipality" => Ok(Self::KlaipedaDistrictMunicipality), + "Kretinga District Municipality" => Ok(Self::KretingaDistrictMunicipality), + "Kupiškis District Municipality" => Ok(Self::KupiskisDistrictMunicipality), + "Kėdainiai District Municipality" => Ok(Self::KedainiaiDistrictMunicipality), + "Lazdijai District Municipality" => Ok(Self::LazdijaiDistrictMunicipality), + "Marijampolė County" => Ok(Self::MarijampoleCounty), + "Marijampolė Municipality" => Ok(Self::MarijampoleMunicipality), + "Mažeikiai District Municipality" => Ok(Self::MazeikiaiDistrictMunicipality), + "Molėtai District Municipality" => Ok(Self::MoletaiDistrictMunicipality), + "Neringa Municipality" => Ok(Self::NeringaMunicipality), + "Pagėgiai municipality" => Ok(Self::PagegiaiMunicipality), + "Pakruojis District Municipality" => Ok(Self::PakruojisDistrictMunicipality), + "Palanga City Municipality" => Ok(Self::PalangaCityMunicipality), + "Panevėžys City Municipality" => Ok(Self::PanevezysCityMunicipality), + "Panevėžys County" => Ok(Self::PanevezysCounty), + "Panevėžys District Municipality" => Ok(Self::PanevezysDistrictMunicipality), + "Pasvalys District Municipality" => Ok(Self::PasvalysDistrictMunicipality), + "Plungė District Municipality" => Ok(Self::PlungeDistrictMunicipality), + "Prienai District Municipality" => Ok(Self::PrienaiDistrictMunicipality), + "Radviliškis District Municipality" => Ok(Self::RadviliskisDistrictMunicipality), + "Raseiniai District Municipality" => Ok(Self::RaseiniaiDistrictMunicipality), + "Rietavas municipality" => Ok(Self::RietavasMunicipality), + "Rokiškis District Municipality" => Ok(Self::RokiskisDistrictMunicipality), + "Skuodas District Municipality" => Ok(Self::SkuodasDistrictMunicipality), + "Tauragė County" => Ok(Self::TaurageCounty), + "Tauragė District Municipality" => Ok(Self::TaurageDistrictMunicipality), + "Telšiai County" => Ok(Self::TelsiaiCounty), + "Telšiai District Municipality" => Ok(Self::TelsiaiDistrictMunicipality), + "Trakai District Municipality" => Ok(Self::TrakaiDistrictMunicipality), + "Ukmergė District Municipality" => Ok(Self::UkmergeDistrictMunicipality), + "Utena County" => Ok(Self::UtenaCounty), + "Utena District Municipality" => Ok(Self::UtenaDistrictMunicipality), + "Varėna District Municipality" => Ok(Self::VarenaDistrictMunicipality), + "Vilkaviškis District Municipality" => Ok(Self::VilkaviskisDistrictMunicipality), + "Vilnius City Municipality" => Ok(Self::VilniusCityMunicipality), + "Vilnius County" => Ok(Self::VilniusCounty), + "Vilnius District Municipality" => Ok(Self::VilniusDistrictMunicipality), + "Visaginas Municipality" => Ok(Self::VisaginasMunicipality), + "Zarasai District Municipality" => Ok(Self::ZarasaiDistrictMunicipality), + "Šakiai District Municipality" => Ok(Self::SakiaiDistrictMunicipality), + "Šalčininkai District Municipality" => Ok(Self::SalcininkaiDistrictMunicipality), + "Šiauliai City Municipality" => Ok(Self::SiauliaiCityMunicipality), + "Šiauliai County" => Ok(Self::SiauliaiCounty), + "Šiauliai District Municipality" => Ok(Self::SiauliaiDistrictMunicipality), + "Šilalė District Municipality" => Ok(Self::SilaleDistrictMunicipality), + "Šilutė District Municipality" => Ok(Self::SiluteDistrictMunicipality), + "Širvintos District Municipality" => Ok(Self::SirvintosDistrictMunicipality), + "Švenčionys District Municipality" => Ok(Self::SvencionysDistrictMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3353,33 +3392,27 @@ impl ForeignTryFrom for LithuaniaStatesAbbreviation { impl ForeignTryFrom for LiechtensteinStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "LiechtensteinStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LiechtensteinStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "balzers" => Ok(Self::Balzers), - "eschen" => Ok(Self::Eschen), - "gamprin" => Ok(Self::Gamprin), - "mauren" => Ok(Self::Mauren), - "planken" => Ok(Self::Planken), - "ruggell" => Ok(Self::Ruggell), - "schaan" => Ok(Self::Schaan), - "schellenberg" => Ok(Self::Schellenberg), - "triesen" => Ok(Self::Triesen), - "triesenberg" => Ok(Self::Triesenberg), - "vaduz" => Ok(Self::Vaduz), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Balzers" => Ok(Self::Balzers), + "Eschen" => Ok(Self::Eschen), + "Gamprin" => Ok(Self::Gamprin), + "Mauren" => Ok(Self::Mauren), + "Planken" => Ok(Self::Planken), + "Ruggell" => Ok(Self::Ruggell), + "Schaan" => Ok(Self::Schaan), + "Schellenberg" => Ok(Self::Schellenberg), + "Triesen" => Ok(Self::Triesen), + "Triesenberg" => Ok(Self::Triesenberg), + "Vaduz" => Ok(Self::Vaduz), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3388,118 +3421,114 @@ impl ForeignTryFrom for LatviaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "LatviaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "LatviaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aglona municipality" => Ok(Self::AglonaMunicipality), - "aizkraukle municipality" => Ok(Self::AizkraukleMunicipality), - "aizpute municipality" => Ok(Self::AizputeMunicipality), - "akniiste municipality" => Ok(Self::AknīsteMunicipality), - "aloja municipality" => Ok(Self::AlojaMunicipality), - "alsunga municipality" => Ok(Self::AlsungaMunicipality), - "aluksne municipality" => Ok(Self::AlūksneMunicipality), - "amata municipality" => Ok(Self::AmataMunicipality), - "ape municipality" => Ok(Self::ApeMunicipality), - "auce municipality" => Ok(Self::AuceMunicipality), - "babite municipality" => Ok(Self::BabīteMunicipality), - "baldone municipality" => Ok(Self::BaldoneMunicipality), - "baltinava municipality" => Ok(Self::BaltinavaMunicipality), - "balvi municipality" => Ok(Self::BalviMunicipality), - "bauska municipality" => Ok(Self::BauskaMunicipality), - "beverina municipality" => Ok(Self::BeverīnaMunicipality), - "broceni municipality" => Ok(Self::BrocēniMunicipality), - "burtnieki municipality" => Ok(Self::BurtniekiMunicipality), - "carnikava municipality" => Ok(Self::CarnikavaMunicipality), - "cesvaine municipality" => Ok(Self::CesvaineMunicipality), - "cibla municipality" => Ok(Self::CiblaMunicipality), - "cesis municipality" => Ok(Self::CēsisMunicipality), - "dagda municipality" => Ok(Self::DagdaMunicipality), - "daugavpils" => Ok(Self::Daugavpils), - "daugavpils municipality" => Ok(Self::DaugavpilsMunicipality), - "dobele municipality" => Ok(Self::DobeleMunicipality), - "dundaga municipality" => Ok(Self::DundagaMunicipality), - "durbe municipality" => Ok(Self::DurbeMunicipality), - "engure municipality" => Ok(Self::EngureMunicipality), - "garkalne municipality" => Ok(Self::GarkalneMunicipality), - "grobina municipality" => Ok(Self::GrobiņaMunicipality), - "gulbene municipality" => Ok(Self::GulbeneMunicipality), - "iecava municipality" => Ok(Self::IecavaMunicipality), - "ikskile municipality" => Ok(Self::IkšķileMunicipality), - "ilukste municipality" => Ok(Self::IlūksteMunicipality), - "incukalns municipality" => Ok(Self::InčukalnsMunicipality), - "jaunjelgava municipality" => Ok(Self::JaunjelgavaMunicipality), - "jaunpiebalga municipality" => Ok(Self::JaunpiebalgaMunicipality), - "jaunpils municipality" => Ok(Self::JaunpilsMunicipality), - "jelgava" => Ok(Self::Jelgava), - "jelgava municipality" => Ok(Self::JelgavaMunicipality), - "jekabpils" => Ok(Self::Jēkabpils), - "jekabpils municipality" => Ok(Self::JēkabpilsMunicipality), - "jurmala" => Ok(Self::Jūrmala), - "kandava municipality" => Ok(Self::KandavaMunicipality), - "koceni municipality" => Ok(Self::KocēniMunicipality), - "koknese municipality" => Ok(Self::KokneseMunicipality), - "krimulda municipality" => Ok(Self::KrimuldaMunicipality), - "kustpils municipality" => Ok(Self::KrustpilsMunicipality), - "kraslava municipality" => Ok(Self::KrāslavaMunicipality), - "kuldiga municipality" => Ok(Self::KuldīgaMunicipality), - "karsava municipality" => Ok(Self::KārsavaMunicipality), - "lielvarde municipality" => Ok(Self::LielvārdeMunicipality), - "liepaja" => Ok(Self::Liepāja), - "limbazi municipality" => Ok(Self::LimbažiMunicipality), - "lubana municipality" => Ok(Self::LubānaMunicipality), - "ludza municipality" => Ok(Self::LudzaMunicipality), - "ligatne municipality" => Ok(Self::LīgatneMunicipality), - "livani municipality" => Ok(Self::LīvāniMunicipality), - "madona municipality" => Ok(Self::MadonaMunicipality), - "mazsalaca municipality" => Ok(Self::MazsalacaMunicipality), - "malpils municipality" => Ok(Self::MālpilsMunicipality), - "marupe municipality" => Ok(Self::MārupeMunicipality), - "mersrags municipality" => Ok(Self::MērsragsMunicipality), - "naukseni municipality" => Ok(Self::NaukšēniMunicipality), - "nereta municipality" => Ok(Self::NeretaMunicipality), - "nica municipality" => Ok(Self::NīcaMunicipality), - "ogre municipality" => Ok(Self::OgreMunicipality), - "olaine municipality" => Ok(Self::OlaineMunicipality), - "ozolnieki municipality" => Ok(Self::OzolniekiMunicipality), - "preili municipality" => Ok(Self::PreiļiMunicipality), - "priekule municipality" => Ok(Self::PriekuleMunicipality), - "priekuli municipality" => Ok(Self::PriekuļiMunicipality), - "pargauja municipality" => Ok(Self::PārgaujaMunicipality), - "pavilosta municipality" => Ok(Self::PāvilostaMunicipality), - "plavinas municipality" => Ok(Self::PļaviņasMunicipality), - "rauna municipality" => Ok(Self::RaunaMunicipality), - "riebini municipality" => Ok(Self::RiebiņiMunicipality), - "riga" => Ok(Self::Riga), - "roja municipality" => Ok(Self::RojaMunicipality), - "ropazi municipality" => Ok(Self::RopažiMunicipality), - "rucava municipality" => Ok(Self::RucavaMunicipality), - "rugaji municipality" => Ok(Self::RugājiMunicipality), - "rundale municipality" => Ok(Self::RundāleMunicipality), - "rezekne" => Ok(Self::Rēzekne), - "rezekne municipality" => Ok(Self::RēzekneMunicipality), - "rujiena municipality" => Ok(Self::RūjienaMunicipality), - "sala municipality" => Ok(Self::SalaMunicipality), - "salacgriva municipality" => Ok(Self::SalacgrīvaMunicipality), - "salaspils municipality" => Ok(Self::SalaspilsMunicipality), - "saldus municipality" => Ok(Self::SaldusMunicipality), - "saulkrasti municipality" => Ok(Self::SaulkrastiMunicipality), - "sigulda municipality" => Ok(Self::SiguldaMunicipality), - "skrunda municipality" => Ok(Self::SkrundaMunicipality), - "skriveri municipality" => Ok(Self::SkrīveriMunicipality), - "smiltene municipality" => Ok(Self::SmilteneMunicipality), - "stopini municipality" => Ok(Self::StopiņiMunicipality), - "strenci municipality" => Ok(Self::StrenčiMunicipality), - "seja municipality" => Ok(Self::SējaMunicipality), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aglona Municipality" => Ok(Self::AglonaMunicipality), + "Aizkraukle Municipality" => Ok(Self::AizkraukleMunicipality), + "Aizpute Municipality" => Ok(Self::AizputeMunicipality), + "Aknīste Municipality" => Ok(Self::AknīsteMunicipality), + "Aloja Municipality" => Ok(Self::AlojaMunicipality), + "Alsunga Municipality" => Ok(Self::AlsungaMunicipality), + "Alūksne Municipality" => Ok(Self::AlūksneMunicipality), + "Amata Municipality" => Ok(Self::AmataMunicipality), + "Ape Municipality" => Ok(Self::ApeMunicipality), + "Auce Municipality" => Ok(Self::AuceMunicipality), + "Babīte Municipality" => Ok(Self::BabīteMunicipality), + "Baldone Municipality" => Ok(Self::BaldoneMunicipality), + "Baltinava Municipality" => Ok(Self::BaltinavaMunicipality), + "Balvi Municipality" => Ok(Self::BalviMunicipality), + "Bauska Municipality" => Ok(Self::BauskaMunicipality), + "Beverīna Municipality" => Ok(Self::BeverīnaMunicipality), + "Brocēni Municipality" => Ok(Self::BrocēniMunicipality), + "Burtnieki Municipality" => Ok(Self::BurtniekiMunicipality), + "Carnikava Municipality" => Ok(Self::CarnikavaMunicipality), + "Cesvaine Municipality" => Ok(Self::CesvaineMunicipality), + "Cibla Municipality" => Ok(Self::CiblaMunicipality), + "Cēsis Municipality" => Ok(Self::CēsisMunicipality), + "Dagda Municipality" => Ok(Self::DagdaMunicipality), + "Daugavpils" => Ok(Self::Daugavpils), + "Daugavpils Municipality" => Ok(Self::DaugavpilsMunicipality), + "Dobele Municipality" => Ok(Self::DobeleMunicipality), + "Dundaga Municipality" => Ok(Self::DundagaMunicipality), + "Durbe Municipality" => Ok(Self::DurbeMunicipality), + "Engure Municipality" => Ok(Self::EngureMunicipality), + "Garkalne Municipality" => Ok(Self::GarkalneMunicipality), + "Grobiņa Municipality" => Ok(Self::GrobiņaMunicipality), + "Gulbene Municipality" => Ok(Self::GulbeneMunicipality), + "Iecava Municipality" => Ok(Self::IecavaMunicipality), + "Ikšķile Municipality" => Ok(Self::IkšķileMunicipality), + "Ilūkste Municipalityy" => Ok(Self::IlūksteMunicipality), + "Inčukalns Municipality" => Ok(Self::InčukalnsMunicipality), + "Jaunjelgava Municipality" => Ok(Self::JaunjelgavaMunicipality), + "Jaunpiebalga Municipality" => Ok(Self::JaunpiebalgaMunicipality), + "Jaunpils Municipality" => Ok(Self::JaunpilsMunicipality), + "Jelgava" => Ok(Self::Jelgava), + "Jelgava Municipality" => Ok(Self::JelgavaMunicipality), + "Jēkabpils" => Ok(Self::Jēkabpils), + "Jēkabpils Municipality" => Ok(Self::JēkabpilsMunicipality), + "Jūrmala" => Ok(Self::Jūrmala), + "Kandava Municipality" => Ok(Self::KandavaMunicipality), + "Kocēni Municipality" => Ok(Self::KocēniMunicipality), + "Koknese Municipality" => Ok(Self::KokneseMunicipality), + "Krimulda Municipality" => Ok(Self::KrimuldaMunicipality), + "Krustpils Municipality" => Ok(Self::KrustpilsMunicipality), + "Krāslava Municipality" => Ok(Self::KrāslavaMunicipality), + "Kuldīga Municipality" => Ok(Self::KuldīgaMunicipality), + "Kārsava Municipality" => Ok(Self::KārsavaMunicipality), + "Lielvārde Municipality" => Ok(Self::LielvārdeMunicipality), + "Liepāja" => Ok(Self::Liepāja), + "Limbaži Municipality" => Ok(Self::LimbažiMunicipality), + "Lubāna Municipality" => Ok(Self::LubānaMunicipality), + "Ludza Municipality" => Ok(Self::LudzaMunicipality), + "Līgatne Municipality" => Ok(Self::LīgatneMunicipality), + "Līvāni Municipality" => Ok(Self::LīvāniMunicipality), + "Madona Municipality" => Ok(Self::MadonaMunicipality), + "Mazsalaca Municipality" => Ok(Self::MazsalacaMunicipality), + "Mālpils Municipality" => Ok(Self::MālpilsMunicipality), + "Mārupe Municipality" => Ok(Self::MārupeMunicipality), + "Mērsrags Municipality" => Ok(Self::MērsragsMunicipality), + "Naukšēni Municipality" => Ok(Self::NaukšēniMunicipality), + "Nereta Municipality" => Ok(Self::NeretaMunicipality), + "Nīca Municipality" => Ok(Self::NīcaMunicipality), + "Ogre Municipality" => Ok(Self::OgreMunicipality), + "Olaine Municipality" => Ok(Self::OlaineMunicipality), + "Ozolnieki Municipality" => Ok(Self::OzolniekiMunicipality), + "Preiļi Municipality" => Ok(Self::PreiļiMunicipality), + "Priekule Municipality" => Ok(Self::PriekuleMunicipality), + "Priekuļi Municipality" => Ok(Self::PriekuļiMunicipality), + "Pārgauja Municipality" => Ok(Self::PārgaujaMunicipality), + "Pāvilosta Municipality" => Ok(Self::PāvilostaMunicipality), + "Pļaviņas Municipality" => Ok(Self::PļaviņasMunicipality), + "Rauna Municipality" => Ok(Self::RaunaMunicipality), + "Riebiņi Municipality" => Ok(Self::RiebiņiMunicipality), + "Riga" => Ok(Self::Riga), + "Roja Municipality" => Ok(Self::RojaMunicipality), + "Ropaži Municipality" => Ok(Self::RopažiMunicipality), + "Rucava Municipality" => Ok(Self::RucavaMunicipality), + "Rugāji Municipality" => Ok(Self::RugājiMunicipality), + "Rundāle Municipality" => Ok(Self::RundāleMunicipality), + "Rēzekne" => Ok(Self::Rēzekne), + "Rēzekne Municipality" => Ok(Self::RēzekneMunicipality), + "Rūjiena Municipality" => Ok(Self::RūjienaMunicipality), + "Sala Municipality" => Ok(Self::SalaMunicipality), + "Salacgrīva Municipality" => Ok(Self::SalacgrīvaMunicipality), + "Salaspils Municipality" => Ok(Self::SalaspilsMunicipality), + "Saldus Municipality" => Ok(Self::SaldusMunicipality), + "Saulkrasti Municipality" => Ok(Self::SaulkrastiMunicipality), + "Sigulda Municipality" => Ok(Self::SiguldaMunicipality), + "Skrunda Municipality" => Ok(Self::SkrundaMunicipality), + "Skrīveri Municipality" => Ok(Self::SkrīveriMunicipality), + "Smiltene Municipality" => Ok(Self::SmilteneMunicipality), + "Stopiņi Municipality" => Ok(Self::StopiņiMunicipality), + "Strenči Municipality" => Ok(Self::StrenčiMunicipality), + "Sēja Municipality" => Ok(Self::SējaMunicipality), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3508,87 +3537,83 @@ impl ForeignTryFrom for MaltaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "MaltaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "MaltaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "attard" => Ok(Self::Attard), - "balzan" => Ok(Self::Balzan), - "birgu" => Ok(Self::Birgu), - "birkirkara" => Ok(Self::Birkirkara), - "birzebbuga" => Ok(Self::Birzebbuga), - "cospicua" => Ok(Self::Cospicua), - "dingli" => Ok(Self::Dingli), - "fgura" => Ok(Self::Fgura), - "floriana" => Ok(Self::Floriana), - "fontana" => Ok(Self::Fontana), - "gudja" => Ok(Self::Gudja), - "gzira" => Ok(Self::Gzira), - "ghajnsielem" => Ok(Self::Ghajnsielem), - "gharb" => Ok(Self::Gharb), - "gharghur" => Ok(Self::Gharghur), - "ghasri" => Ok(Self::Ghasri), - "ghaxaq" => Ok(Self::Ghaxaq), - "hamrun" => Ok(Self::Hamrun), - "iklin" => Ok(Self::Iklin), - "senglea" => Ok(Self::Senglea), - "kalkara" => Ok(Self::Kalkara), - "kercem" => Ok(Self::Kercem), - "kirkop" => Ok(Self::Kirkop), - "lija" => Ok(Self::Lija), - "luqa" => Ok(Self::Luqa), - "marsa" => Ok(Self::Marsa), - "marsaskala" => Ok(Self::Marsaskala), - "marsaxlokk" => Ok(Self::Marsaxlokk), - "mdina" => Ok(Self::Mdina), - "mellieha" => Ok(Self::Mellieha), - "mgarr" => Ok(Self::Mgarr), - "mosta" => Ok(Self::Mosta), - "mqabba" => Ok(Self::Mqabba), - "msida" => Ok(Self::Msida), - "mtarfa" => Ok(Self::Mtarfa), - "munxar" => Ok(Self::Munxar), - "nadur" => Ok(Self::Nadur), - "naxxar" => Ok(Self::Naxxar), - "paola" => Ok(Self::Paola), - "pembroke" => Ok(Self::Pembroke), - "pieta" => Ok(Self::Pieta), - "qala" => Ok(Self::Qala), - "qormi" => Ok(Self::Qormi), - "qrendi" => Ok(Self::Qrendi), - "victoria" => Ok(Self::Victoria), - "rabat" => Ok(Self::Rabat), - "st julians" => Ok(Self::StJulians), - "san gwann" => Ok(Self::SanGwann), - "saint lawrence" => Ok(Self::SaintLawrence), - "st pauls bay" => Ok(Self::StPaulsBay), - "sannat" => Ok(Self::Sannat), - "santa lucija" => Ok(Self::SantaLucija), - "santa venera" => Ok(Self::SantaVenera), - "siggiewi" => Ok(Self::Siggiewi), - "sliema" => Ok(Self::Sliema), - "swieqi" => Ok(Self::Swieqi), - "ta xbiex" => Ok(Self::TaXbiex), - "tarxien" => Ok(Self::Tarxien), - "valletta" => Ok(Self::Valletta), - "xaghra" => Ok(Self::Xaghra), - "xewkija" => Ok(Self::Xewkija), - "xghajra" => Ok(Self::Xghajra), - "zabbar" => Ok(Self::Zabbar), - "zebbug gozo" => Ok(Self::ZebbugGozo), - "zebbug malta" => Ok(Self::ZebbugMalta), - "zejtun" => Ok(Self::Zejtun), - "zurrieq" => Ok(Self::Zurrieq), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Attard" => Ok(Self::Attard), + "Balzan" => Ok(Self::Balzan), + "Birgu" => Ok(Self::Birgu), + "Birkirkara" => Ok(Self::Birkirkara), + "Birżebbuġa" => Ok(Self::Birżebbuġa), + "Cospicua" => Ok(Self::Cospicua), + "Dingli" => Ok(Self::Dingli), + "Fgura" => Ok(Self::Fgura), + "Floriana" => Ok(Self::Floriana), + "Fontana" => Ok(Self::Fontana), + "Gudja" => Ok(Self::Gudja), + "Gżira" => Ok(Self::Gżira), + "Għajnsielem" => Ok(Self::Għajnsielem), + "Għarb" => Ok(Self::Għarb), + "Għargħur" => Ok(Self::Għargħur), + "Għasri" => Ok(Self::Għasri), + "Għaxaq" => Ok(Self::Għaxaq), + "Ħamrun" => Ok(Self::Ħamrun), + "Iklin" => Ok(Self::Iklin), + "Senglea" => Ok(Self::Senglea), + "Kalkara" => Ok(Self::Kalkara), + "Kerċem" => Ok(Self::Kerċem), + "Kirkop" => Ok(Self::Kirkop), + "Lija" => Ok(Self::Lija), + "Luqa" => Ok(Self::Luqa), + "Marsa" => Ok(Self::Marsa), + "Marsaskala" => Ok(Self::Marsaskala), + "Marsaxlokk" => Ok(Self::Marsaxlokk), + "Mdina" => Ok(Self::Mdina), + "Mellieħa" => Ok(Self::Mellieħa), + "Mosta" => Ok(Self::Mosta), + "Mqabba" => Ok(Self::Mqabba), + "Msida" => Ok(Self::Msida), + "Mtarfa" => Ok(Self::Mtarfa), + "Munxar" => Ok(Self::Munxar), + "Mġarr" => Ok(Self::Mġarr), + "Nadur" => Ok(Self::Nadur), + "Naxxar" => Ok(Self::Naxxar), + "Paola" => Ok(Self::Paola), + "Pembroke" => Ok(Self::Pembroke), + "Pietà" => Ok(Self::Pietà), + "Qala" => Ok(Self::Qala), + "Qormi" => Ok(Self::Qormi), + "Qrendi" => Ok(Self::Qrendi), + "Rabat" => Ok(Self::Rabat), + "Saint Lawrence" => Ok(Self::SaintLawrence), + "San Ġwann" => Ok(Self::SanĠwann), + "Sannat" => Ok(Self::Sannat), + "Santa Luċija" => Ok(Self::SantaLuċija), + "Santa Venera" => Ok(Self::SantaVenera), + "Siġġiewi" => Ok(Self::Siġġiewi), + "Sliema" => Ok(Self::Sliema), + "St. Julian's" => Ok(Self::StJulians), + "St. Paul's Bay" => Ok(Self::StPaulsBay), + "Swieqi" => Ok(Self::Swieqi), + "Ta' Xbiex" => Ok(Self::TaXbiex), + "Tarxien" => Ok(Self::Tarxien), + "Valletta" => Ok(Self::Valletta), + "Victoria" => Ok(Self::Victoria), + "Xagħra" => Ok(Self::Xagħra), + "Xewkija" => Ok(Self::Xewkija), + "Xgħajra" => Ok(Self::Xgħajra), + "Żabbar" => Ok(Self::Żabbar), + "Żebbuġ Gozo" => Ok(Self::ŻebbuġGozo), + "Żebbuġ Malta" => Ok(Self::ŻebbuġMalta), + "Żejtun" => Ok(Self::Żejtun), + "Żurrieq" => Ok(Self::Żurrieq), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3596,29 +3621,23 @@ impl ForeignTryFrom for MaltaStatesAbbreviation { impl ForeignTryFrom for BelarusStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BelarusStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BelarusStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "brest region" => Ok(Self::BrestRegion), - "gomel region" => Ok(Self::GomelRegion), - "grodno region" => Ok(Self::GrodnoRegion), - "minsk" => Ok(Self::Minsk), - "minsk region" => Ok(Self::MinskRegion), - "mogilev region" => Ok(Self::MogilevRegion), - "vitebsk region" => Ok(Self::VitebskRegion), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Brest Region" => Ok(Self::BrestRegion), + "Gomel Region" => Ok(Self::GomelRegion), + "Grodno Region" => Ok(Self::GrodnoRegion), + "Minsk" => Ok(Self::Minsk), + "Minsk Region" => Ok(Self::MinskRegion), + "Mogilev Region" => Ok(Self::MogilevRegion), + "Vitebsk Region" => Ok(Self::VitebskRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3626,51 +3645,45 @@ impl ForeignTryFrom for BelarusStatesAbbreviation { impl ForeignTryFrom for IrelandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "IrelandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "IrelandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "connacht" => Ok(Self::Connacht), - "county carlow" => Ok(Self::CountyCarlow), - "county cavan" => Ok(Self::CountyCavan), - "county clare" => Ok(Self::CountyClare), - "county cork" => Ok(Self::CountyCork), - "county donegal" => Ok(Self::CountyDonegal), - "county dublin" => Ok(Self::CountyDublin), - "county galway" => Ok(Self::CountyGalway), - "county kerry" => Ok(Self::CountyKerry), - "county kildare" => Ok(Self::CountyKildare), - "county kilkenny" => Ok(Self::CountyKilkenny), - "county laois" => Ok(Self::CountyLaois), - "county limerick" => Ok(Self::CountyLimerick), - "county longford" => Ok(Self::CountyLongford), - "county louth" => Ok(Self::CountyLouth), - "county mayo" => Ok(Self::CountyMayo), - "county meath" => Ok(Self::CountyMeath), - "county monaghan" => Ok(Self::CountyMonaghan), - "county offaly" => Ok(Self::CountyOffaly), - "county roscommon" => Ok(Self::CountyRoscommon), - "county sligo" => Ok(Self::CountySligo), - "county tipperary" => Ok(Self::CountyTipperary), - "county waterford" => Ok(Self::CountyWaterford), - "county westmeath" => Ok(Self::CountyWestmeath), - "county wexford" => Ok(Self::CountyWexford), - "county wicklow" => Ok(Self::CountyWicklow), - "leinster" => Ok(Self::Leinster), - "munster" => Ok(Self::Munster), - "ulster" => Ok(Self::Ulster), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Connacht" => Ok(Self::Connacht), + "County Carlow" => Ok(Self::CountyCarlow), + "County Cavan" => Ok(Self::CountyCavan), + "County Clare" => Ok(Self::CountyClare), + "County Cork" => Ok(Self::CountyCork), + "County Donegal" => Ok(Self::CountyDonegal), + "County Dublin" => Ok(Self::CountyDublin), + "County Galway" => Ok(Self::CountyGalway), + "County Kerry" => Ok(Self::CountyKerry), + "County Kildare" => Ok(Self::CountyKildare), + "County Kilkenny" => Ok(Self::CountyKilkenny), + "County Laois" => Ok(Self::CountyLaois), + "County Limerick" => Ok(Self::CountyLimerick), + "County Longford" => Ok(Self::CountyLongford), + "County Louth" => Ok(Self::CountyLouth), + "County Mayo" => Ok(Self::CountyMayo), + "County Meath" => Ok(Self::CountyMeath), + "County Monaghan" => Ok(Self::CountyMonaghan), + "County Offaly" => Ok(Self::CountyOffaly), + "County Roscommon" => Ok(Self::CountyRoscommon), + "County Sligo" => Ok(Self::CountySligo), + "County Tipperary" => Ok(Self::CountyTipperary), + "County Waterford" => Ok(Self::CountyWaterford), + "County Westmeath" => Ok(Self::CountyWestmeath), + "County Wexford" => Ok(Self::CountyWexford), + "County Wicklow" => Ok(Self::CountyWicklow), + "Leinster" => Ok(Self::Leinster), + "Munster" => Ok(Self::Munster), + "Ulster" => Ok(Self::Ulster), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3678,30 +3691,24 @@ impl ForeignTryFrom for IrelandStatesAbbreviation { impl ForeignTryFrom for IcelandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "IcelandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "IcelandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "capital region" => Ok(Self::CapitalRegion), - "eastern region" => Ok(Self::EasternRegion), - "northeastern region" => Ok(Self::NortheasternRegion), - "northwestern region" => Ok(Self::NorthwesternRegion), - "southern peninsula region" => Ok(Self::SouthernPeninsulaRegion), - "southern region" => Ok(Self::SouthernRegion), - "western region" => Ok(Self::WesternRegion), - "westfjords" => Ok(Self::Westfjords), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Capital Region" => Ok(Self::CapitalRegion), + "Eastern Region" => Ok(Self::EasternRegion), + "Northeastern Region" => Ok(Self::NortheasternRegion), + "Northwestern Region" => Ok(Self::NorthwesternRegion), + "Southern Peninsula Region" => Ok(Self::SouthernPeninsulaRegion), + "Southern Region" => Ok(Self::SouthernRegion), + "Western Region" => Ok(Self::WesternRegion), + "Westfjords" => Ok(Self::Westfjords), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3709,64 +3716,58 @@ impl ForeignTryFrom for IcelandStatesAbbreviation { impl ForeignTryFrom for HungaryStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "HungaryStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "HungaryStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "baranya county" => Ok(Self::BaranyaCounty), - "borsod abauj zemplen county" => Ok(Self::BorsodAbaujZemplenCounty), - "budapest" => Ok(Self::Budapest), - "bacs kiskun county" => Ok(Self::BacsKiskunCounty), - "bekes county" => Ok(Self::BekesCounty), - "bekescsaba" => Ok(Self::Bekescsaba), - "csongrad county" => Ok(Self::CsongradCounty), - "debrecen" => Ok(Self::Debrecen), - "dunaujvaros" => Ok(Self::Dunaujvaros), - "eger" => Ok(Self::Eger), - "fejer county" => Ok(Self::FejerCounty), - "gyor" => Ok(Self::Gyor), - "gyor moson sopron county" => Ok(Self::GyorMosonSopronCounty), - "hajdu bihar county" => Ok(Self::HajduBiharCounty), - "heves county" => Ok(Self::HevesCounty), - "hodmezovasarhely" => Ok(Self::Hodmezovasarhely), - "jasz nagykun szolnok county" => Ok(Self::JaszNagykunSzolnokCounty), - "kaposvar" => Ok(Self::Kaposvar), - "kecskemet" => Ok(Self::Kecskemet), - "miskolc" => Ok(Self::Miskolc), - "nagykanizsa" => Ok(Self::Nagykanizsa), - "nyiregyhaza" => Ok(Self::Nyiregyhaza), - "nograd county" => Ok(Self::NogradCounty), - "pest county" => Ok(Self::PestCounty), - "pecs" => Ok(Self::Pecs), - "salgotarjan" => Ok(Self::Salgotarjan), - "somogy county" => Ok(Self::SomogyCounty), - "sopron" => Ok(Self::Sopron), - "szabolcs szatmar bereg county" => Ok(Self::SzabolcsSzatmarBeregCounty), - "szeged" => Ok(Self::Szeged), - "szekszard" => Ok(Self::Szekszard), - "szolnok" => Ok(Self::Szolnok), - "szombathely" => Ok(Self::Szombathely), - "szekesfehervar" => Ok(Self::Szekesfehervar), - "tatabanya" => Ok(Self::Tatabanya), - "tolna county" => Ok(Self::TolnaCounty), - "vas county" => Ok(Self::VasCounty), - "veszprem" => Ok(Self::Veszprem), - "veszprem county" => Ok(Self::VeszpremCounty), - "zala county" => Ok(Self::ZalaCounty), - "zalaegerszeg" => Ok(Self::Zalaegerszeg), - "erd" => Ok(Self::Erd), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Baranya County" => Ok(Self::BaranyaCounty), + "Borsod-Abaúj-Zemplén County" => Ok(Self::BorsodAbaujZemplenCounty), + "Budapest" => Ok(Self::Budapest), + "Bács-Kiskun County" => Ok(Self::BacsKiskunCounty), + "Békés County" => Ok(Self::BekesCounty), + "Békéscsaba" => Ok(Self::Bekescsaba), + "Csongrád County" => Ok(Self::CsongradCounty), + "Debrecen" => Ok(Self::Debrecen), + "Dunaújváros" => Ok(Self::Dunaujvaros), + "Eger" => Ok(Self::Eger), + "Fejér County" => Ok(Self::FejerCounty), + "Győr" => Ok(Self::Gyor), + "Győr-Moson-Sopron County" => Ok(Self::GyorMosonSopronCounty), + "Hajdú-Bihar County" => Ok(Self::HajduBiharCounty), + "Heves County" => Ok(Self::HevesCounty), + "Hódmezővásárhely" => Ok(Self::Hodmezovasarhely), + "Jász-Nagykun-Szolnok County" => Ok(Self::JaszNagykunSzolnokCounty), + "Kaposvár" => Ok(Self::Kaposvar), + "Kecskemét" => Ok(Self::Kecskemet), + "Miskolc" => Ok(Self::Miskolc), + "Nagykanizsa" => Ok(Self::Nagykanizsa), + "Nyíregyháza" => Ok(Self::Nyiregyhaza), + "Nógrád County" => Ok(Self::NogradCounty), + "Pest County" => Ok(Self::PestCounty), + "Pécs" => Ok(Self::Pecs), + "Salgótarján" => Ok(Self::Salgotarjan), + "Somogy County" => Ok(Self::SomogyCounty), + "Sopron" => Ok(Self::Sopron), + "Szabolcs-Szatmár-Bereg County" => Ok(Self::SzabolcsSzatmarBeregCounty), + "Szeged" => Ok(Self::Szeged), + "Szekszárd" => Ok(Self::Szekszard), + "Szolnok" => Ok(Self::Szolnok), + "Szombathely" => Ok(Self::Szombathely), + "Székesfehérvár" => Ok(Self::Szekesfehervar), + "Tatabánya" => Ok(Self::Tatabanya), + "Tolna County" => Ok(Self::TolnaCounty), + "Vas County" => Ok(Self::VasCounty), + "Veszprém" => Ok(Self::Veszprem), + "Veszprém County" => Ok(Self::VeszpremCounty), + "Zala County" => Ok(Self::ZalaCounty), + "Zalaegerszeg" => Ok(Self::Zalaegerszeg), + "Érd" => Ok(Self::Erd), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3775,57 +3776,53 @@ impl ForeignTryFrom for GreeceStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "GreeceStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "GreeceStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "achaea regional unit" => Ok(Self::AchaeaRegionalUnit), - "aetolia acarnania regional unit" => Ok(Self::AetoliaAcarnaniaRegionalUnit), - "arcadia prefecture" => Ok(Self::ArcadiaPrefecture), - "argolis regional unit" => Ok(Self::ArgolisRegionalUnit), - "attica region" => Ok(Self::AtticaRegion), - "boeotia regional unit" => Ok(Self::BoeotiaRegionalUnit), - "central greece region" => Ok(Self::CentralGreeceRegion), - "central macedonia" => Ok(Self::CentralMacedonia), - "chania regional unit" => Ok(Self::ChaniaRegionalUnit), - "corfu prefecture" => Ok(Self::CorfuPrefecture), - "corinthia regional unit" => Ok(Self::CorinthiaRegionalUnit), - "crete region" => Ok(Self::CreteRegion), - "drama regional unit" => Ok(Self::DramaRegionalUnit), - "east attica regional unit" => Ok(Self::EastAtticaRegionalUnit), - "east macedonia and thrace" => Ok(Self::EastMacedoniaAndThrace), - "epirus region" => Ok(Self::EpirusRegion), - "euboea" => Ok(Self::Euboea), - "grevena prefecture" => Ok(Self::GrevenaPrefecture), - "imathia regional unit" => Ok(Self::ImathiaRegionalUnit), - "ioannina regional unit" => Ok(Self::IoanninaRegionalUnit), - "ionian islands region" => Ok(Self::IonianIslandsRegion), - "karditsa regional unit" => Ok(Self::KarditsaRegionalUnit), - "kastoria regional unit" => Ok(Self::KastoriaRegionalUnit), - "kefalonia prefecture" => Ok(Self::KefaloniaPrefecture), - "kilkis regional unit" => Ok(Self::KilkisRegionalUnit), - "kozani prefecture" => Ok(Self::KozaniPrefecture), - "laconia" => Ok(Self::Laconia), - "larissa prefecture" => Ok(Self::LarissaPrefecture), - "lefkada regional unit" => Ok(Self::LefkadaRegionalUnit), - "pella regional unit" => Ok(Self::PellaRegionalUnit), - "peloponnese region" => Ok(Self::PeloponneseRegion), - "phthiotis prefecture" => Ok(Self::PhthiotisPrefecture), - "preveza prefecture" => Ok(Self::PrevezaPrefecture), - "serres prefecture" => Ok(Self::SerresPrefecture), - "south aegean" => Ok(Self::SouthAegean), - "thessaloniki regional unit" => Ok(Self::ThessalonikiRegionalUnit), - "west greece region" => Ok(Self::WestGreeceRegion), - "west macedonia region" => Ok(Self::WestMacedoniaRegion), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Achaea Regional Unit" => Ok(Self::AchaeaRegionalUnit), + "Aetolia-Acarnania Regional Unit" => Ok(Self::AetoliaAcarnaniaRegionalUnit), + "Arcadia Prefecture" => Ok(Self::ArcadiaPrefecture), + "Argolis Regional Unit" => Ok(Self::ArgolisRegionalUnit), + "Attica Region" => Ok(Self::AtticaRegion), + "Boeotia Regional Unit" => Ok(Self::BoeotiaRegionalUnit), + "Central Greece Region" => Ok(Self::CentralGreeceRegion), + "Central Macedonia" => Ok(Self::CentralMacedonia), + "Chania Regional Unit" => Ok(Self::ChaniaRegionalUnit), + "Corfu Prefecture" => Ok(Self::CorfuPrefecture), + "Corinthia Regional Unit" => Ok(Self::CorinthiaRegionalUnit), + "Crete Region" => Ok(Self::CreteRegion), + "Drama Regional Unit" => Ok(Self::DramaRegionalUnit), + "East Attica Regional Unit" => Ok(Self::EastAtticaRegionalUnit), + "East Macedonia and Thrace" => Ok(Self::EastMacedoniaAndThrace), + "Epirus Region" => Ok(Self::EpirusRegion), + "Euboea" => Ok(Self::Euboea), + "Grevena Prefecture" => Ok(Self::GrevenaPrefecture), + "Imathia Regional Unit" => Ok(Self::ImathiaRegionalUnit), + "Ioannina Regional Unit" => Ok(Self::IoanninaRegionalUnit), + "Ionian Islands Region" => Ok(Self::IonianIslandsRegion), + "Karditsa Regional Unit" => Ok(Self::KarditsaRegionalUnit), + "Kastoria Regional Unit" => Ok(Self::KastoriaRegionalUnit), + "Kefalonia Prefecture" => Ok(Self::KefaloniaPrefecture), + "Kilkis Regional Unit" => Ok(Self::KilkisRegionalUnit), + "Kozani Prefecture" => Ok(Self::KozaniPrefecture), + "Laconia" => Ok(Self::Laconia), + "Larissa Prefecture" => Ok(Self::LarissaPrefecture), + "Lefkada Regional Unit" => Ok(Self::LefkadaRegionalUnit), + "Pella Regional Unit" => Ok(Self::PellaRegionalUnit), + "Peloponnese Region" => Ok(Self::PeloponneseRegion), + "Phthiotis Prefecture" => Ok(Self::PhthiotisPrefecture), + "Preveza Prefecture" => Ok(Self::PrevezaPrefecture), + "Serres Prefecture" => Ok(Self::SerresPrefecture), + "South Aegean" => Ok(Self::SouthAegean), + "Thessaloniki Regional Unit" => Ok(Self::ThessalonikiRegionalUnit), + "West Greece Region" => Ok(Self::WestGreeceRegion), + "West Macedonia Region" => Ok(Self::WestMacedoniaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3833,43 +3830,37 @@ impl ForeignTryFrom for GreeceStatesAbbreviation { impl ForeignTryFrom for FinlandStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "FinlandStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "FinlandStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "central finland" => Ok(Self::CentralFinland), - "central ostrobothnia" => Ok(Self::CentralOstrobothnia), - "eastern finland province" => Ok(Self::EasternFinlandProvince), - "finland proper" => Ok(Self::FinlandProper), - "kainuu" => Ok(Self::Kainuu), - "kymenlaakso" => Ok(Self::Kymenlaakso), - "lapland" => Ok(Self::Lapland), - "north karelia" => Ok(Self::NorthKarelia), - "northern ostrobothnia" => Ok(Self::NorthernOstrobothnia), - "northern savonia" => Ok(Self::NorthernSavonia), - "ostrobothnia" => Ok(Self::Ostrobothnia), - "oulu province" => Ok(Self::OuluProvince), - "pirkanmaa" => Ok(Self::Pirkanmaa), - "paijanne tavastia" => Ok(Self::PaijanneTavastia), - "satakunta" => Ok(Self::Satakunta), - "south karelia" => Ok(Self::SouthKarelia), - "southern ostrobothnia" => Ok(Self::SouthernOstrobothnia), - "southern savonia" => Ok(Self::SouthernSavonia), - "tavastia proper" => Ok(Self::TavastiaProper), - "uusimaa" => Ok(Self::Uusimaa), - "aland islands" => Ok(Self::AlandIslands), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Central Finland" => Ok(Self::CentralFinland), + "Central Ostrobothnia" => Ok(Self::CentralOstrobothnia), + "Eastern Finland Province" => Ok(Self::EasternFinlandProvince), + "Finland Proper" => Ok(Self::FinlandProper), + "Kainuu" => Ok(Self::Kainuu), + "Kymenlaakso" => Ok(Self::Kymenlaakso), + "Lapland" => Ok(Self::Lapland), + "North Karelia" => Ok(Self::NorthKarelia), + "Northern Ostrobothnia" => Ok(Self::NorthernOstrobothnia), + "Northern Savonia" => Ok(Self::NorthernSavonia), + "Ostrobothnia" => Ok(Self::Ostrobothnia), + "Oulu Province" => Ok(Self::OuluProvince), + "Pirkanmaa" => Ok(Self::Pirkanmaa), + "Päijänne Tavastia" => Ok(Self::PaijanneTavastia), + "Satakunta" => Ok(Self::Satakunta), + "South Karelia" => Ok(Self::SouthKarelia), + "Southern Ostrobothnia" => Ok(Self::SouthernOstrobothnia), + "Southern Savonia" => Ok(Self::SouthernSavonia), + "Tavastia Proper" => Ok(Self::TavastiaProper), + "Uusimaa" => Ok(Self::Uusimaa), + "Åland Islands" => Ok(Self::AlandIslands), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3877,27 +3868,21 @@ impl ForeignTryFrom for FinlandStatesAbbreviation { impl ForeignTryFrom for DenmarkStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "DenmarkStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "DenmarkStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "capital region of denmark" => Ok(Self::CapitalRegionOfDenmark), - "central denmark region" => Ok(Self::CentralDenmarkRegion), - "north denmark region" => Ok(Self::NorthDenmarkRegion), - "region zealand" => Ok(Self::RegionZealand), - "region of southern denmark" => Ok(Self::RegionOfSouthernDenmark), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Capital Region of Denmark" => Ok(Self::CapitalRegionOfDenmark), + "Central Denmark Region" => Ok(Self::CentralDenmarkRegion), + "North Denmark Region" => Ok(Self::NorthDenmarkRegion), + "Region Zealand" => Ok(Self::RegionZealand), + "Region of Southern Denmark" => Ok(Self::RegionOfSouthernDenmark), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -3905,129 +3890,123 @@ impl ForeignTryFrom for DenmarkStatesAbbreviation { impl ForeignTryFrom for CzechRepublicStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "CzechRepublicStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "CzechRepublicStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "benesov district" => Ok(Self::BenesovDistrict), - "beroun district" => Ok(Self::BerounDistrict), - "blansko district" => Ok(Self::BlanskoDistrict), - "brno city district" => Ok(Self::BrnoCityDistrict), - "brno country district" => Ok(Self::BrnoCountryDistrict), - "bruntal district" => Ok(Self::BruntalDistrict), - "breclav district" => Ok(Self::BreclavDistrict), - "central bohemian region" => Ok(Self::CentralBohemianRegion), - "cheb district" => Ok(Self::ChebDistrict), - "chomutov district" => Ok(Self::ChomutovDistrict), - "chrudim district" => Ok(Self::ChrudimDistrict), - "domazlice district" => Ok(Self::DomazliceDistrict), - "decin district" => Ok(Self::DecinDistrict), - "frydek mistek district" => Ok(Self::FrydekMistekDistrict), - "havlickuv brod district" => Ok(Self::HavlickuvBrodDistrict), - "hodonin district" => Ok(Self::HodoninDistrict), - "horni pocernice" => Ok(Self::HorniPocernice), - "hradec kralove district" => Ok(Self::HradecKraloveDistrict), - "hradec kralove region" => Ok(Self::HradecKraloveRegion), - "jablonec nad nisou district" => Ok(Self::JablonecNadNisouDistrict), - "jesenik district" => Ok(Self::JesenikDistrict), - "jihlava district" => Ok(Self::JihlavaDistrict), - "jindrichuv hradec district" => Ok(Self::JindrichuvHradecDistrict), - "jicin district" => Ok(Self::JicinDistrict), - "karlovy vary district" => Ok(Self::KarlovyVaryDistrict), - "karlovy vary region" => Ok(Self::KarlovyVaryRegion), - "karvina district" => Ok(Self::KarvinaDistrict), - "kladno district" => Ok(Self::KladnoDistrict), - "klatovy district" => Ok(Self::KlatovyDistrict), - "kolin district" => Ok(Self::KolinDistrict), - "kromeriz district" => Ok(Self::KromerizDistrict), - "liberec district" => Ok(Self::LiberecDistrict), - "liberec region" => Ok(Self::LiberecRegion), - "litomerice district" => Ok(Self::LitomericeDistrict), - "louny district" => Ok(Self::LounyDistrict), - "mlada boleslav district" => Ok(Self::MladaBoleslavDistrict), - "moravian silesian region" => Ok(Self::MoravianSilesianRegion), - "most district" => Ok(Self::MostDistrict), - "melnik district" => Ok(Self::MelnikDistrict), - "novy jicin district" => Ok(Self::NovyJicinDistrict), - "nymburk district" => Ok(Self::NymburkDistrict), - "nachod district" => Ok(Self::NachodDistrict), - "olomouc district" => Ok(Self::OlomoucDistrict), - "olomouc region" => Ok(Self::OlomoucRegion), - "opava district" => Ok(Self::OpavaDistrict), - "ostrava city district" => Ok(Self::OstravaCityDistrict), - "pardubice district" => Ok(Self::PardubiceDistrict), - "pardubice region" => Ok(Self::PardubiceRegion), - "pelhrimov district" => Ok(Self::PelhrimovDistrict), - "plzen region" => Ok(Self::PlzenRegion), - "plzen city district" => Ok(Self::PlzenCityDistrict), - "plzen north district" => Ok(Self::PlzenNorthDistrict), - "plzen south district" => Ok(Self::PlzenSouthDistrict), - "prachatice district" => Ok(Self::PrachaticeDistrict), - "prague" => Ok(Self::Prague), - "prague1" => Ok(Self::Prague1), - "prague10" => Ok(Self::Prague10), - "prague11" => Ok(Self::Prague11), - "prague12" => Ok(Self::Prague12), - "prague13" => Ok(Self::Prague13), - "prague14" => Ok(Self::Prague14), - "prague15" => Ok(Self::Prague15), - "prague16" => Ok(Self::Prague16), - "prague2" => Ok(Self::Prague2), - "prague21" => Ok(Self::Prague21), - "prague3" => Ok(Self::Prague3), - "prague4" => Ok(Self::Prague4), - "prague5" => Ok(Self::Prague5), - "prague6" => Ok(Self::Prague6), - "prague7" => Ok(Self::Prague7), - "prague8" => Ok(Self::Prague8), - "prague9" => Ok(Self::Prague9), - "prague east district" => Ok(Self::PragueEastDistrict), - "prague west district" => Ok(Self::PragueWestDistrict), - "prostejov district" => Ok(Self::ProstejovDistrict), - "pisek district" => Ok(Self::PisekDistrict), - "prerov district" => Ok(Self::PrerovDistrict), - "pribram district" => Ok(Self::PribramDistrict), - "rakovnik district" => Ok(Self::RakovnikDistrict), - "rokycany district" => Ok(Self::RokycanyDistrict), - "rychnov nad kneznou district" => Ok(Self::RychnovNadKneznouDistrict), - "semily district" => Ok(Self::SemilyDistrict), - "sokolov district" => Ok(Self::SokolovDistrict), - "south bohemian region" => Ok(Self::SouthBohemianRegion), - "south moravian region" => Ok(Self::SouthMoravianRegion), - "strakonice district" => Ok(Self::StrakoniceDistrict), - "svitavy district" => Ok(Self::SvitavyDistrict), - "tachov district" => Ok(Self::TachovDistrict), - "teplice district" => Ok(Self::TepliceDistrict), - "trutnov district" => Ok(Self::TrutnovDistrict), - "tabor district" => Ok(Self::TaborDistrict), - "trebic district" => Ok(Self::TrebicDistrict), - "uherske hradiste district" => Ok(Self::UherskeHradisteDistrict), - "vsetin district" => Ok(Self::VsetinDistrict), - "vysocina region" => Ok(Self::VysocinaRegion), - "vyskov district" => Ok(Self::VyskovDistrict), - "zlin district" => Ok(Self::ZlinDistrict), - "zlin region" => Ok(Self::ZlinRegion), - "znojmo district" => Ok(Self::ZnojmoDistrict), - "usti nad labem district" => Ok(Self::UstiNadLabemDistrict), - "usti nad labem region" => Ok(Self::UstiNadLabemRegion), - "usti nad orlici district" => Ok(Self::UstiNadOrliciDistrict), - "ceska lipa district" => Ok(Self::CeskaLipaDistrict), - "ceske budejovice district" => Ok(Self::CeskeBudejoviceDistrict), - "cesky krumlov district" => Ok(Self::CeskyKrumlovDistrict), - "sumperk district" => Ok(Self::SumperkDistrict), - "zdar nad sazavou district" => Ok(Self::ZdarNadSazavouDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Benešov District" => Ok(Self::BenesovDistrict), + "Beroun District" => Ok(Self::BerounDistrict), + "Blansko District" => Ok(Self::BlanskoDistrict), + "Brno-City District" => Ok(Self::BrnoCityDistrict), + "Brno-Country District" => Ok(Self::BrnoCountryDistrict), + "Bruntál District" => Ok(Self::BruntalDistrict), + "Břeclav District" => Ok(Self::BreclavDistrict), + "Central Bohemian Region" => Ok(Self::CentralBohemianRegion), + "Cheb District" => Ok(Self::ChebDistrict), + "Chomutov District" => Ok(Self::ChomutovDistrict), + "Chrudim District" => Ok(Self::ChrudimDistrict), + "Domažlice Distric" => Ok(Self::DomazliceDistrict), + "Děčín District" => Ok(Self::DecinDistrict), + "Frýdek-Místek District" => Ok(Self::FrydekMistekDistrict), + "Havlíčkův Brod District" => Ok(Self::HavlickuvBrodDistrict), + "Hodonín District" => Ok(Self::HodoninDistrict), + "Horní Počernice" => Ok(Self::HorniPocernice), + "Hradec Králové District" => Ok(Self::HradecKraloveDistrict), + "Hradec Králové Region" => Ok(Self::HradecKraloveRegion), + "Jablonec nad Nisou District" => Ok(Self::JablonecNadNisouDistrict), + "Jeseník District" => Ok(Self::JesenikDistrict), + "Jihlava District" => Ok(Self::JihlavaDistrict), + "Jindřichův Hradec District" => Ok(Self::JindrichuvHradecDistrict), + "Jičín District" => Ok(Self::JicinDistrict), + "Karlovy Vary District" => Ok(Self::KarlovyVaryDistrict), + "Karlovy Vary Region" => Ok(Self::KarlovyVaryRegion), + "Karviná District" => Ok(Self::KarvinaDistrict), + "Kladno District" => Ok(Self::KladnoDistrict), + "Klatovy District" => Ok(Self::KlatovyDistrict), + "Kolín District" => Ok(Self::KolinDistrict), + "Kroměříž District" => Ok(Self::KromerizDistrict), + "Liberec District" => Ok(Self::LiberecDistrict), + "Liberec Region" => Ok(Self::LiberecRegion), + "Litoměřice District" => Ok(Self::LitomericeDistrict), + "Louny District" => Ok(Self::LounyDistrict), + "Mladá Boleslav District" => Ok(Self::MladaBoleslavDistrict), + "Moravian-Silesian Region" => Ok(Self::MoravianSilesianRegion), + "Most District" => Ok(Self::MostDistrict), + "Mělník District" => Ok(Self::MelnikDistrict), + "Nový Jičín District" => Ok(Self::NovyJicinDistrict), + "Nymburk District" => Ok(Self::NymburkDistrict), + "Náchod District" => Ok(Self::NachodDistrict), + "Olomouc District" => Ok(Self::OlomoucDistrict), + "Olomouc Region" => Ok(Self::OlomoucRegion), + "Opava District" => Ok(Self::OpavaDistrict), + "Ostrava-City District" => Ok(Self::OstravaCityDistrict), + "Pardubice District" => Ok(Self::PardubiceDistrict), + "Pardubice Region" => Ok(Self::PardubiceRegion), + "Pelhřimov District" => Ok(Self::PelhrimovDistrict), + "Plzeň Region" => Ok(Self::PlzenRegion), + "Plzeň-City District" => Ok(Self::PlzenCityDistrict), + "Plzeň-North District" => Ok(Self::PlzenNorthDistrict), + "Plzeň-South District" => Ok(Self::PlzenSouthDistrict), + "Prachatice District" => Ok(Self::PrachaticeDistrict), + "Prague" => Ok(Self::Prague), + "Prague 1" => Ok(Self::Prague1), + "Prague 10" => Ok(Self::Prague10), + "Prague 11" => Ok(Self::Prague11), + "Prague 12" => Ok(Self::Prague12), + "Prague 13" => Ok(Self::Prague13), + "Prague 14" => Ok(Self::Prague14), + "Prague 15" => Ok(Self::Prague15), + "Prague 16" => Ok(Self::Prague16), + "Prague 2" => Ok(Self::Prague2), + "Prague 21" => Ok(Self::Prague21), + "Prague 3" => Ok(Self::Prague3), + "Prague 4" => Ok(Self::Prague4), + "Prague 5" => Ok(Self::Prague5), + "Prague 6" => Ok(Self::Prague6), + "Prague 7" => Ok(Self::Prague7), + "Prague 8" => Ok(Self::Prague8), + "Prague 9" => Ok(Self::Prague9), + "Prague-East District" => Ok(Self::PragueEastDistrict), + "Prague-West District" => Ok(Self::PragueWestDistrict), + "Prostějov District" => Ok(Self::ProstejovDistrict), + "Písek District" => Ok(Self::PisekDistrict), + "Přerov District" => Ok(Self::PrerovDistrict), + "Příbram District" => Ok(Self::PribramDistrict), + "Rakovník District" => Ok(Self::RakovnikDistrict), + "Rokycany District" => Ok(Self::RokycanyDistrict), + "Rychnov nad Kněžnou District" => Ok(Self::RychnovNadKneznouDistrict), + "Semily District" => Ok(Self::SemilyDistrict), + "Sokolov District" => Ok(Self::SokolovDistrict), + "South Bohemian Region" => Ok(Self::SouthBohemianRegion), + "South Moravian Region" => Ok(Self::SouthMoravianRegion), + "Strakonice District" => Ok(Self::StrakoniceDistrict), + "Svitavy District" => Ok(Self::SvitavyDistrict), + "Tachov District" => Ok(Self::TachovDistrict), + "Teplice District" => Ok(Self::TepliceDistrict), + "Trutnov District" => Ok(Self::TrutnovDistrict), + "Tábor District" => Ok(Self::TaborDistrict), + "Třebíč District" => Ok(Self::TrebicDistrict), + "Uherské Hradiště District" => Ok(Self::UherskeHradisteDistrict), + "Vsetín District" => Ok(Self::VsetinDistrict), + "Vysočina Region" => Ok(Self::VysocinaRegion), + "Vyškov District" => Ok(Self::VyskovDistrict), + "Zlín District" => Ok(Self::ZlinDistrict), + "Zlín Region" => Ok(Self::ZlinRegion), + "Znojmo District" => Ok(Self::ZnojmoDistrict), + "Ústí nad Labem District" => Ok(Self::UstiNadLabemDistrict), + "Ústí nad Labem Region" => Ok(Self::UstiNadLabemRegion), + "Ústí nad Orlicí District" => Ok(Self::UstiNadOrliciDistrict), + "Česká Lípa District" => Ok(Self::CeskaLipaDistrict), + "České Budějovice District" => Ok(Self::CeskeBudejoviceDistrict), + "Český Krumlov District" => Ok(Self::CeskyKrumlovDistrict), + "Šumperk District" => Ok(Self::SumperkDistrict), + "Žďár nad Sázavou District" => Ok(Self::ZdarNadSazavouDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4035,42 +4014,36 @@ impl ForeignTryFrom for CzechRepublicStatesAbbreviation { impl ForeignTryFrom for CroatiaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "CroatiaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "CroatiaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bjelovar bilogora county" => Ok(Self::BjelovarBilogoraCounty), - "brod posavina county" => Ok(Self::BrodPosavinaCounty), - "dubrovnik neretva county" => Ok(Self::DubrovnikNeretvaCounty), - "istria county" => Ok(Self::IstriaCounty), - "koprivnica krizevci county" => Ok(Self::KoprivnicaKrizevciCounty), - "krapina zagorje county" => Ok(Self::KrapinaZagorjeCounty), - "lika senj county" => Ok(Self::LikaSenjCounty), - "medimurje county" => Ok(Self::MedimurjeCounty), - "osijek baranja county" => Ok(Self::OsijekBaranjaCounty), - "pozega slavonian county" => Ok(Self::PozegaSlavoniaCounty), - "primorje gorski kotar county" => Ok(Self::PrimorjeGorskiKotarCounty), - "sisak moslavina county" => Ok(Self::SisakMoslavinaCounty), - "split dalmatia county" => Ok(Self::SplitDalmatiaCounty), - "varazdin county" => Ok(Self::VarazdinCounty), - "virovitica podravina county" => Ok(Self::ViroviticaPodravinaCounty), - "vukovar syrmia county" => Ok(Self::VukovarSyrmiaCounty), - "zadar county" => Ok(Self::ZadarCounty), - "zagreb" => Ok(Self::Zagreb), - "zagreb county" => Ok(Self::ZagrebCounty), - "sibenik knin county" => Ok(Self::SibenikKninCounty), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bjelovar-Bilogora County" => Ok(Self::BjelovarBilogoraCounty), + "Brod-Posavina County" => Ok(Self::BrodPosavinaCounty), + "Dubrovnik-Neretva County" => Ok(Self::DubrovnikNeretvaCounty), + "Istria County" => Ok(Self::IstriaCounty), + "Koprivnica-Križevci County" => Ok(Self::KoprivnicaKrizevciCounty), + "Krapina-Zagorje County" => Ok(Self::KrapinaZagorjeCounty), + "Lika-Senj County" => Ok(Self::LikaSenjCounty), + "Međimurje County" => Ok(Self::MedimurjeCounty), + "Osijek-Baranja County" => Ok(Self::OsijekBaranjaCounty), + "Požega-Slavonia County" => Ok(Self::PozegaSlavoniaCounty), + "Primorje-Gorski Kotar County" => Ok(Self::PrimorjeGorskiKotarCounty), + "Sisak-Moslavina County" => Ok(Self::SisakMoslavinaCounty), + "Split-Dalmatia County" => Ok(Self::SplitDalmatiaCounty), + "Varaždin County" => Ok(Self::VarazdinCounty), + "Virovitica-Podravina County" => Ok(Self::ViroviticaPodravinaCounty), + "Vukovar-Syrmia County" => Ok(Self::VukovarSyrmiaCounty), + "Zadar County" => Ok(Self::ZadarCounty), + "Zagreb" => Ok(Self::Zagreb), + "Zagreb County" => Ok(Self::ZagrebCounty), + "Šibenik-Knin County" => Ok(Self::SibenikKninCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4078,50 +4051,44 @@ impl ForeignTryFrom for CroatiaStatesAbbreviation { impl ForeignTryFrom for BulgariaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BulgariaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BulgariaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "blagoevgrad province" => Ok(Self::BlagoevgradProvince), - "burgas province" => Ok(Self::BurgasProvince), - "dobrich province" => Ok(Self::DobrichProvince), - "gabrovo province" => Ok(Self::GabrovoProvince), - "haskovo province" => Ok(Self::HaskovoProvince), - "kardzhali province" => Ok(Self::KardzhaliProvince), - "kyustendil province" => Ok(Self::KyustendilProvince), - "lovech province" => Ok(Self::LovechProvince), - "montana province" => Ok(Self::MontanaProvince), - "pazardzhik province" => Ok(Self::PazardzhikProvince), - "pernik province" => Ok(Self::PernikProvince), - "pleven province" => Ok(Self::PlevenProvince), - "plovdiv province" => Ok(Self::PlovdivProvince), - "razgrad province" => Ok(Self::RazgradProvince), - "ruse province" => Ok(Self::RuseProvince), - "shumen" => Ok(Self::Shumen), - "silistra province" => Ok(Self::SilistraProvince), - "sliven province" => Ok(Self::SlivenProvince), - "smolyan province" => Ok(Self::SmolyanProvince), - "sofia city province" => Ok(Self::SofiaCityProvince), - "sofia province" => Ok(Self::SofiaProvince), - "stara zagora province" => Ok(Self::StaraZagoraProvince), - "targovishte province" => Ok(Self::TargovishteProvince), - "varna province" => Ok(Self::VarnaProvince), - "veliko tarnovo province" => Ok(Self::VelikoTarnovoProvince), - "vidin province" => Ok(Self::VidinProvince), - "vratsa province" => Ok(Self::VratsaProvince), - "yambol province" => Ok(Self::YambolProvince), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Blagoevgrad Province" => Ok(Self::BlagoevgradProvince), + "Burgas Province" => Ok(Self::BurgasProvince), + "Dobrich Province" => Ok(Self::DobrichProvince), + "Gabrovo Province" => Ok(Self::GabrovoProvince), + "Haskovo Province" => Ok(Self::HaskovoProvince), + "Kardzhali Province" => Ok(Self::KardzhaliProvince), + "Kyustendil Province" => Ok(Self::KyustendilProvince), + "Lovech Province" => Ok(Self::LovechProvince), + "Montana Province" => Ok(Self::MontanaProvince), + "Pazardzhik Province" => Ok(Self::PazardzhikProvince), + "Pernik Province" => Ok(Self::PernikProvince), + "Pleven Province" => Ok(Self::PlevenProvince), + "Plovdiv Province" => Ok(Self::PlovdivProvince), + "Razgrad Province" => Ok(Self::RazgradProvince), + "Ruse Province" => Ok(Self::RuseProvince), + "Shumen" => Ok(Self::Shumen), + "Silistra Province" => Ok(Self::SilistraProvince), + "Sliven Province" => Ok(Self::SlivenProvince), + "Smolyan Province" => Ok(Self::SmolyanProvince), + "Sofia City Province" => Ok(Self::SofiaCityProvince), + "Sofia Province" => Ok(Self::SofiaProvince), + "Stara Zagora Province" => Ok(Self::StaraZagoraProvince), + "Targovishte Provinc" => Ok(Self::TargovishteProvince), + "Varna Province" => Ok(Self::VarnaProvince), + "Veliko Tarnovo Province" => Ok(Self::VelikoTarnovoProvince), + "Vidin Province" => Ok(Self::VidinProvince), + "Vratsa Province" => Ok(Self::VratsaProvince), + "Yambol Province" => Ok(Self::YambolProvince), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4129,37 +4096,31 @@ impl ForeignTryFrom for BulgariaStatesAbbreviation { impl ForeignTryFrom for BosniaAndHerzegovinaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BosniaAndHerzegovinaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BosniaAndHerzegovinaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "bosnian podrinje canton" => Ok(Self::BosnianPodrinjeCanton), - "brcko district" => Ok(Self::BrckoDistrict), - "canton 10" => Ok(Self::Canton10), - "central bosnia canton" => Ok(Self::CentralBosniaCanton), - "federation of bosnia and herzegovina" => { - Ok(Self::FederationOfBosniaAndHerzegovina) - } - "herzegovina neretva canton" => Ok(Self::HerzegovinaNeretvaCanton), - "posavina canton" => Ok(Self::PosavinaCanton), - "republika srpska" => Ok(Self::RepublikaSrpska), - "sarajevo canton" => Ok(Self::SarajevoCanton), - "tuzla canton" => Ok(Self::TuzlaCanton), - "una sana canton" => Ok(Self::UnaSanaCanton), - "west herzegovina canton" => Ok(Self::WestHerzegovinaCanton), - "zenica doboj canton" => Ok(Self::ZenicaDobojCanton), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Bosnian Podrinje Canton" => Ok(Self::BosnianPodrinjeCanton), + "Brčko District" => Ok(Self::BrckoDistrict), + "Canton 10" => Ok(Self::Canton10), + "Central Bosnia Canton" => Ok(Self::CentralBosniaCanton), + "Federation of Bosnia and Herzegovina" => { + Ok(Self::FederationOfBosniaAndHerzegovina) } - } + "Herzegovina-Neretva Canton" => Ok(Self::HerzegovinaNeretvaCanton), + "Posavina Canton" => Ok(Self::PosavinaCanton), + "Republika Srpska" => Ok(Self::RepublikaSrpska), + "Sarajevo Canton" => Ok(Self::SarajevoCanton), + "Tuzla Canton" => Ok(Self::TuzlaCanton), + "Una-Sana Canton" => Ok(Self::UnaSanaCanton), + "West Herzegovina Canton" => Ok(Self::WestHerzegovinaCanton), + "Zenica-Doboj Canton" => Ok(Self::ZenicaDobojCanton), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -4167,242 +4128,275 @@ impl ForeignTryFrom for BosniaAndHerzegovinaStatesAbbreviation { impl ForeignTryFrom for UnitedKingdomStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "UnitedKingdomStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "UnitedKingdomStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "aberdeen city" => Ok(Self::AberdeenCity), - "aberdeenshire" => Ok(Self::Aberdeenshire), - "angus" => Ok(Self::Angus), - "antrim and newtownabbey" => Ok(Self::AntrimAndNewtownabbey), - "ards and north down" => Ok(Self::ArdsAndNorthDown), - "argyll and bute" => Ok(Self::ArgyllAndBute), - "armagh city banbridge and craigavon" => { - Ok(Self::ArmaghCityBanbridgeAndCraigavon) - } - "barking and dagenham" => Ok(Self::BarkingAndDagenham), - "barnet" => Ok(Self::Barnet), - "barnsley" => Ok(Self::Barnsley), - "bath and north east somerset" => Ok(Self::BathAndNorthEastSomerset), - "bedford" => Ok(Self::Bedford), - "belfast city" => Ok(Self::BelfastCity), - "bexley" => Ok(Self::Bexley), - "birmingham" => Ok(Self::Birmingham), - "blackburn with darwen" => Ok(Self::BlackburnWithDarwen), - "blackpool" => Ok(Self::Blackpool), - "blaenau gwent" => Ok(Self::BlaenauGwent), - "bolton" => Ok(Self::Bolton), - "bournemouth christchurch and poole" => { - Ok(Self::BournemouthChristchurchAndPoole) - } - "bracknell forest" => Ok(Self::BracknellForest), - "bradford" => Ok(Self::Bradford), - "brent" => Ok(Self::Brent), - "bridgend" => Ok(Self::Bridgend), - "brighton and hove" => Ok(Self::BrightonAndHove), - "bristol city of" => Ok(Self::BristolCityOf), - "bromley" => Ok(Self::Bromley), - "buckinghamshire" => Ok(Self::Buckinghamshire), - "bury" => Ok(Self::Bury), - "caerphilly" => Ok(Self::Caerphilly), - "calderdale" => Ok(Self::Calderdale), - "cambridgeshire" => Ok(Self::Cambridgeshire), - "camden" => Ok(Self::Camden), - "cardiff" => Ok(Self::Cardiff), - "carmarthenshire" => Ok(Self::Carmarthenshire), - "causeway coast and glens" => Ok(Self::CausewayCoastAndGlens), - "central bedfordshire" => Ok(Self::CentralBedfordshire), - "ceredigion" => Ok(Self::Ceredigion), - "cheshire east" => Ok(Self::CheshireEast), - "cheshire west and chester" => Ok(Self::CheshireWestAndChester), - "clackmannanshire" => Ok(Self::Clackmannanshire), - "conwy" => Ok(Self::Conwy), - "cornwall" => Ok(Self::Cornwall), - "coventry" => Ok(Self::Coventry), - "croydon" => Ok(Self::Croydon), - "cumbria" => Ok(Self::Cumbria), - "darlington" => Ok(Self::Darlington), - "denbighshire" => Ok(Self::Denbighshire), - "derby" => Ok(Self::Derby), - "derbyshire" => Ok(Self::Derbyshire), - "derry and strabane" => Ok(Self::DerryAndStrabane), - "devon" => Ok(Self::Devon), - "doncaster" => Ok(Self::Doncaster), - "dorset" => Ok(Self::Dorset), - "dudley" => Ok(Self::Dudley), - "dumfries and galloway" => Ok(Self::DumfriesAndGalloway), - "dundee city" => Ok(Self::DundeeCity), - "durham county" => Ok(Self::DurhamCounty), - "ealing" => Ok(Self::Ealing), - "east ayrshire" => Ok(Self::EastAyrshire), - "east dunbartonshire" => Ok(Self::EastDunbartonshire), - "east lothian" => Ok(Self::EastLothian), - "east renfrewshire" => Ok(Self::EastRenfrewshire), - "east riding of yorkshire" => Ok(Self::EastRidingOfYorkshire), - "east sussex" => Ok(Self::EastSussex), - "edinburgh city of" => Ok(Self::EdinburghCityOf), - "eilean siar" => Ok(Self::EileanSiar), - "enfield" => Ok(Self::Enfield), - "essex" => Ok(Self::Essex), - "falkirk" => Ok(Self::Falkirk), - "fermanagh and omagh" => Ok(Self::FermanaghAndOmagh), - "fife" => Ok(Self::Fife), - "flintshire" => Ok(Self::Flintshire), - "gateshead" => Ok(Self::Gateshead), - "glasgow city" => Ok(Self::GlasgowCity), - "gloucestershire" => Ok(Self::Gloucestershire), - "greenwich" => Ok(Self::Greenwich), - "gwynedd" => Ok(Self::Gwynedd), - "hackney" => Ok(Self::Hackney), - "halton" => Ok(Self::Halton), - "hammersmith and fulham" => Ok(Self::HammersmithAndFulham), - "hampshire" => Ok(Self::Hampshire), - "haringey" => Ok(Self::Haringey), - "harrow" => Ok(Self::Harrow), - "hartlepool" => Ok(Self::Hartlepool), - "havering" => Ok(Self::Havering), - "herefordshire" => Ok(Self::Herefordshire), - "hertfordshire" => Ok(Self::Hertfordshire), - "highland" => Ok(Self::Highland), - "hillingdon" => Ok(Self::Hillingdon), - "hounslow" => Ok(Self::Hounslow), - "inverclyde" => Ok(Self::Inverclyde), - "isle of anglesey" => Ok(Self::IsleOfAnglesey), - "isle of wight" => Ok(Self::IsleOfWight), - "isles of scilly" => Ok(Self::IslesOfScilly), - "islington" => Ok(Self::Islington), - "kensington and chelsea" => Ok(Self::KensingtonAndChelsea), - "kent" => Ok(Self::Kent), - "kingston upon hull" => Ok(Self::KingstonUponHull), - "kingston upon thames" => Ok(Self::KingstonUponThames), - "kirklees" => Ok(Self::Kirklees), - "knowsley" => Ok(Self::Knowsley), - "lambeth" => Ok(Self::Lambeth), - "lancashire" => Ok(Self::Lancashire), - "leeds" => Ok(Self::Leeds), - "leicester" => Ok(Self::Leicester), - "leicestershire" => Ok(Self::Leicestershire), - "lewisham" => Ok(Self::Lewisham), - "lincolnshire" => Ok(Self::Lincolnshire), - "lisburn and castlereagh" => Ok(Self::LisburnAndCastlereagh), - "liverpool" => Ok(Self::Liverpool), - "london city of" => Ok(Self::LondonCityOf), - "luton" => Ok(Self::Luton), - "manchester" => Ok(Self::Manchester), - "medway" => Ok(Self::Medway), - "merthyr tydfil" => Ok(Self::MerthyrTydfil), - "merton" => Ok(Self::Merton), - "mid and east antrim" => Ok(Self::MidAndEastAntrim), - "mid ulster" => Ok(Self::MidUlster), - "middlesbrough" => Ok(Self::Middlesbrough), - "midlothian" => Ok(Self::Midlothian), - "milton keynes" => Ok(Self::MiltonKeynes), - "monmouthshire" => Ok(Self::Monmouthshire), - "moray" => Ok(Self::Moray), - "neath port talbot" => Ok(Self::NeathPortTalbot), - "newcastle upon tyne" => Ok(Self::NewcastleUponTyne), - "newham" => Ok(Self::Newham), - "newport" => Ok(Self::Newport), - "newry mourne and down" => Ok(Self::NewryMourneAndDown), - "norfolk" => Ok(Self::Norfolk), - "north ayrshire" => Ok(Self::NorthAyrshire), - "north east lincolnshire" => Ok(Self::NorthEastLincolnshire), - "north lanarkshire" => Ok(Self::NorthLanarkshire), - "north lincolnshire" => Ok(Self::NorthLincolnshire), - "north somerset" => Ok(Self::NorthSomerset), - "north tyneside" => Ok(Self::NorthTyneside), - "north yorkshire" => Ok(Self::NorthYorkshire), - "northamptonshire" => Ok(Self::Northamptonshire), - "northumberland" => Ok(Self::Northumberland), - "nottingham" => Ok(Self::Nottingham), - "nottinghamshire" => Ok(Self::Nottinghamshire), - "oldham" => Ok(Self::Oldham), - "orkney islands" => Ok(Self::OrkneyIslands), - "oxfordshire" => Ok(Self::Oxfordshire), - "pembrokeshire" => Ok(Self::Pembrokeshire), - "perth and kinross" => Ok(Self::PerthAndKinross), - "peterborough" => Ok(Self::Peterborough), - "plymouth" => Ok(Self::Plymouth), - "portsmouth" => Ok(Self::Portsmouth), - "powys" => Ok(Self::Powys), - "reading" => Ok(Self::Reading), - "redbridge" => Ok(Self::Redbridge), - "redcar and cleveland" => Ok(Self::RedcarAndCleveland), - "renfrewshire" => Ok(Self::Renfrewshire), - "rhondda cynon taff" => Ok(Self::RhonddaCynonTaff), - "richmond upon thames" => Ok(Self::RichmondUponThames), - "rochdale" => Ok(Self::Rochdale), - "rotherham" => Ok(Self::Rotherham), - "rutland" => Ok(Self::Rutland), - "salford" => Ok(Self::Salford), - "sandwell" => Ok(Self::Sandwell), - "scottish borders" => Ok(Self::ScottishBorders), - "sefton" => Ok(Self::Sefton), - "sheffield" => Ok(Self::Sheffield), - "shetland islands" => Ok(Self::ShetlandIslands), - "shropshire" => Ok(Self::Shropshire), - "slough" => Ok(Self::Slough), - "solihull" => Ok(Self::Solihull), - "somerset" => Ok(Self::Somerset), - "south ayrshire" => Ok(Self::SouthAyrshire), - "south gloucestershire" => Ok(Self::SouthGloucestershire), - "south lanarkshire" => Ok(Self::SouthLanarkshire), - "south tyneside" => Ok(Self::SouthTyneside), - "southampton" => Ok(Self::Southampton), - "southend on sea" => Ok(Self::SouthendOnSea), - "southwark" => Ok(Self::Southwark), - "st helens" => Ok(Self::StHelens), - "staffordshire" => Ok(Self::Staffordshire), - "stirling" => Ok(Self::Stirling), - "stockport" => Ok(Self::Stockport), - "stockton on tees" => Ok(Self::StocktonOnTees), - "stoke on trent" => Ok(Self::StokeOnTrent), - "suffolk" => Ok(Self::Suffolk), - "sunderland" => Ok(Self::Sunderland), - "surrey" => Ok(Self::Surrey), - "sutton" => Ok(Self::Sutton), - "swansea" => Ok(Self::Swansea), - "swindon" => Ok(Self::Swindon), - "tameside" => Ok(Self::Tameside), - "telford and wrekin" => Ok(Self::TelfordAndWrekin), - "thurrock" => Ok(Self::Thurrock), - "torbay" => Ok(Self::Torbay), - "torfaen" => Ok(Self::Torfaen), - "tower hamlets" => Ok(Self::TowerHamlets), - "trafford" => Ok(Self::Trafford), - "vale of glamorgan" => Ok(Self::ValeOfGlamorgan), - "wakefield" => Ok(Self::Wakefield), - "walsall" => Ok(Self::Walsall), - "waltham forest" => Ok(Self::WalthamForest), - "wandsworth" => Ok(Self::Wandsworth), - "warrington" => Ok(Self::Warrington), - "warwickshire" => Ok(Self::Warwickshire), - "west berkshire" => Ok(Self::WestBerkshire), - "west dunbartonshire" => Ok(Self::WestDunbartonshire), - "west lothian" => Ok(Self::WestLothian), - "west sussex" => Ok(Self::WestSussex), - "westminster" => Ok(Self::Westminster), - "wigan" => Ok(Self::Wigan), - "wiltshire" => Ok(Self::Wiltshire), - "windsor and maidenhead" => Ok(Self::WindsorAndMaidenhead), - "wirral" => Ok(Self::Wirral), - "wokingham" => Ok(Self::Wokingham), - "wolverhampton" => Ok(Self::Wolverhampton), - "worcestershire" => Ok(Self::Worcestershire), - "wrexham" => Ok(Self::Wrexham), - "york" => Ok(Self::York), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Aberdeen" => Ok(Self::Aberdeen), + "Aberdeenshire" => Ok(Self::Aberdeenshire), + "Angus" => Ok(Self::Angus), + "Antrim" => Ok(Self::Antrim), + "Antrim and Newtownabbey" => Ok(Self::AntrimAndNewtownabbey), + "Ards" => Ok(Self::Ards), + "Ards and North Down" => Ok(Self::ArdsAndNorthDown), + "Argyll and Bute" => Ok(Self::ArgyllAndBute), + "Armagh City and District Council" => Ok(Self::ArmaghCityAndDistrictCouncil), + "Armagh, Banbridge and Craigavon" => Ok(Self::ArmaghBanbridgeAndCraigavon), + "Ascension Island" => Ok(Self::AscensionIsland), + "Ballymena Borough" => Ok(Self::BallymenaBorough), + "Ballymoney" => Ok(Self::Ballymoney), + "Banbridge" => Ok(Self::Banbridge), + "Barnsley" => Ok(Self::Barnsley), + "Bath and North East Somerset" => Ok(Self::BathAndNorthEastSomerset), + "Bedford" => Ok(Self::Bedford), + "Belfast district" => Ok(Self::BelfastDistrict), + "Birmingham" => Ok(Self::Birmingham), + "Blackburn with Darwen" => Ok(Self::BlackburnWithDarwen), + "Blackpool" => Ok(Self::Blackpool), + "Blaenau Gwent County Borough" => Ok(Self::BlaenauGwentCountyBorough), + "Bolton" => Ok(Self::Bolton), + "Bournemouth" => Ok(Self::Bournemouth), + "Bracknell Forest" => Ok(Self::BracknellForest), + "Bradford" => Ok(Self::Bradford), + "Bridgend County Borough" => Ok(Self::BridgendCountyBorough), + "Brighton and Hove" => Ok(Self::BrightonAndHove), + "Buckinghamshire" => Ok(Self::Buckinghamshire), + "Bury" => Ok(Self::Bury), + "Caerphilly County Borough" => Ok(Self::CaerphillyCountyBorough), + "Calderdale" => Ok(Self::Calderdale), + "Cambridgeshire" => Ok(Self::Cambridgeshire), + "Carmarthenshire" => Ok(Self::Carmarthenshire), + "Carrickfergus Borough Council" => Ok(Self::CarrickfergusBoroughCouncil), + "Castlereagh" => Ok(Self::Castlereagh), + "Causeway Coast and Glens" => Ok(Self::CausewayCoastAndGlens), + "Central Bedfordshire" => Ok(Self::CentralBedfordshire), + "Ceredigion" => Ok(Self::Ceredigion), + "Cheshire East" => Ok(Self::CheshireEast), + "Cheshire West and Chester" => Ok(Self::CheshireWestAndChester), + "City and County of Cardiff" => Ok(Self::CityAndCountyOfCardiff), + "City and County of Swansea" => Ok(Self::CityAndCountyOfSwansea), + "City of Bristol" => Ok(Self::CityOfBristol), + "City of Derby" => Ok(Self::CityOfDerby), + "City of Kingston upon Hull" => Ok(Self::CityOfKingstonUponHull), + "City of Leicester" => Ok(Self::CityOfLeicester), + "City of London" => Ok(Self::CityOfLondon), + "City of Nottingham" => Ok(Self::CityOfNottingham), + "City of Peterborough" => Ok(Self::CityOfPeterborough), + "City of Plymouth" => Ok(Self::CityOfPlymouth), + "City of Portsmouth" => Ok(Self::CityOfPortsmouth), + "City of Southampton" => Ok(Self::CityOfSouthampton), + "City of Stoke-on-Trent" => Ok(Self::CityOfStokeOnTrent), + "City of Sunderland" => Ok(Self::CityOfSunderland), + "City of Westminster" => Ok(Self::CityOfWestminster), + "City of Wolverhampton" => Ok(Self::CityOfWolverhampton), + "City of York" => Ok(Self::CityOfYork), + "Clackmannanshire" => Ok(Self::Clackmannanshire), + "Coleraine Borough Council" => Ok(Self::ColeraineBoroughCouncil), + "Conwy County Borough" => Ok(Self::ConwyCountyBorough), + "Cookstown District Council" => Ok(Self::CookstownDistrictCouncil), + "Cornwall" => Ok(Self::Cornwall), + "County Durham" => Ok(Self::CountyDurham), + "Coventry" => Ok(Self::Coventry), + "Craigavon Borough Council" => Ok(Self::CraigavonBoroughCouncil), + "Cumbria" => Ok(Self::Cumbria), + "Darlington" => Ok(Self::Darlington), + "Denbighshire" => Ok(Self::Denbighshire), + "Derbyshire" => Ok(Self::Derbyshire), + "Derry City and Strabane" => Ok(Self::DerryCityAndStrabane), + "Derry City Council" => Ok(Self::DerryCityCouncil), + "Devon" => Ok(Self::Devon), + "Doncaster" => Ok(Self::Doncaster), + "Dorset" => Ok(Self::Dorset), + "Down District Council" => Ok(Self::DownDistrictCouncil), + "Dudley" => Ok(Self::Dudley), + "Dumfries and Galloway" => Ok(Self::DumfriesAndGalloway), + "Dundee" => Ok(Self::Dundee), + "Dungannon and South Tyrone Borough Council" => { + Ok(Self::DungannonAndSouthTyroneBoroughCouncil) } - } + "East Ayrshire" => Ok(Self::EastAyrshire), + "East Dunbartonshire" => Ok(Self::EastDunbartonshire), + "East Lothian" => Ok(Self::EastLothian), + "East Renfrewshire" => Ok(Self::EastRenfrewshire), + "East Riding of Yorkshire" => Ok(Self::EastRidingOfYorkshire), + "East Sussex" => Ok(Self::EastSussex), + "Edinburgh" => Ok(Self::Edinburgh), + "England" => Ok(Self::England), + "Essex" => Ok(Self::Essex), + "Falkirk" => Ok(Self::Falkirk), + "Fermanagh and Omagh" => Ok(Self::FermanaghAndOmagh), + "Fermanagh District Council" => Ok(Self::FermanaghDistrictCouncil), + "Fife" => Ok(Self::Fife), + "Flintshire" => Ok(Self::Flintshire), + "Gateshead" => Ok(Self::Gateshead), + "Glasgow" => Ok(Self::Glasgow), + "Gloucestershire" => Ok(Self::Gloucestershire), + "Gwynedd" => Ok(Self::Gwynedd), + "Halton" => Ok(Self::Halton), + "Hampshire" => Ok(Self::Hampshire), + "Hartlepool" => Ok(Self::Hartlepool), + "Herefordshire" => Ok(Self::Herefordshire), + "Hertfordshire" => Ok(Self::Hertfordshire), + "Highland" => Ok(Self::Highland), + "Inverclyde" => Ok(Self::Inverclyde), + "Isle of Wight" => Ok(Self::IsleOfWight), + "Isles of Scilly" => Ok(Self::IslesOfScilly), + "Kent" => Ok(Self::Kent), + "Kirklees" => Ok(Self::Kirklees), + "Knowsley" => Ok(Self::Knowsley), + "Lancashire" => Ok(Self::Lancashire), + "Larne Borough Council" => Ok(Self::LarneBoroughCouncil), + "Leeds" => Ok(Self::Leeds), + "Leicestershire" => Ok(Self::Leicestershire), + "Limavady Borough Council" => Ok(Self::LimavadyBoroughCouncil), + "Lincolnshire" => Ok(Self::Lincolnshire), + "Lisburn and Castlereagh" => Ok(Self::LisburnAndCastlereagh), + "Lisburn City Council" => Ok(Self::LisburnCityCouncil), + "Liverpool" => Ok(Self::Liverpool), + "London Borough of Barking and Dagenham" => { + Ok(Self::LondonBoroughOfBarkingAndDagenham) + } + "London Borough of Barnet" => Ok(Self::LondonBoroughOfBarnet), + "London Borough of Bexley" => Ok(Self::LondonBoroughOfBexley), + "London Borough of Brent" => Ok(Self::LondonBoroughOfBrent), + "London Borough of Bromley" => Ok(Self::LondonBoroughOfBromley), + "London Borough of Camden" => Ok(Self::LondonBoroughOfCamden), + "London Borough of Croydon" => Ok(Self::LondonBoroughOfCroydon), + "London Borough of Ealing" => Ok(Self::LondonBoroughOfEaling), + "London Borough of Enfield" => Ok(Self::LondonBoroughOfEnfield), + "London Borough of Hackney" => Ok(Self::LondonBoroughOfHackney), + "London Borough of Hammersmith and Fulham" => { + Ok(Self::LondonBoroughOfHammersmithAndFulham) + } + "London Borough of Haringey" => Ok(Self::LondonBoroughOfHaringey), + "London Borough of Harrow" => Ok(Self::LondonBoroughOfHarrow), + "London Borough of Havering" => Ok(Self::LondonBoroughOfHavering), + "London Borough of Hillingdon" => Ok(Self::LondonBoroughOfHillingdon), + "London Borough of Hounslow" => Ok(Self::LondonBoroughOfHounslow), + "London Borough of Islington" => Ok(Self::LondonBoroughOfIslington), + "London Borough of Lambeth" => Ok(Self::LondonBoroughOfLambeth), + "London Borough of Lewisham" => Ok(Self::LondonBoroughOfLewisham), + "London Borough of Merton" => Ok(Self::LondonBoroughOfMerton), + "London Borough of Newham" => Ok(Self::LondonBoroughOfNewham), + "London Borough of Redbridge" => Ok(Self::LondonBoroughOfRedbridge), + "London Borough of Richmond upon Thames" => { + Ok(Self::LondonBoroughOfRichmondUponThames) + } + "London Borough of Southwark" => Ok(Self::LondonBoroughOfSouthwark), + "London Borough of Sutton" => Ok(Self::LondonBoroughOfSutton), + "London Borough of Tower Hamlets" => Ok(Self::LondonBoroughOfTowerHamlets), + "London Borough of Waltham Forest" => Ok(Self::LondonBoroughOfWalthamForest), + "London Borough of Wandsworth" => Ok(Self::LondonBoroughOfWandsworth), + "Magherafelt District Council" => Ok(Self::MagherafeltDistrictCouncil), + "Manchester" => Ok(Self::Manchester), + "Medway" => Ok(Self::Medway), + "Merthyr Tydfil County Borough" => Ok(Self::MerthyrTydfilCountyBorough), + "Metropolitan Borough of Wigan" => Ok(Self::MetropolitanBoroughOfWigan), + "Mid and East Antrim" => Ok(Self::MidAndEastAntrim), + "Mid Ulster" => Ok(Self::MidUlster), + "Middlesbrough" => Ok(Self::Middlesbrough), + "Midlothian" => Ok(Self::Midlothian), + "Milton Keynes" => Ok(Self::MiltonKeynes), + "Monmouthshire" => Ok(Self::Monmouthshire), + "Moray" => Ok(Self::Moray), + "Moyle District Council" => Ok(Self::MoyleDistrictCouncil), + "Neath Port Talbot County Borough" => Ok(Self::NeathPortTalbotCountyBorough), + "Newcastle upon Tyne" => Ok(Self::NewcastleUponTyne), + "Newport" => Ok(Self::Newport), + "Newry and Mourne District Council" => Ok(Self::NewryAndMourneDistrictCouncil), + "Newry, Mourne and Down" => Ok(Self::NewryMourneAndDown), + "Newtownabbey Borough Council" => Ok(Self::NewtownabbeyBoroughCouncil), + "Norfolk" => Ok(Self::Norfolk), + "North Ayrshire" => Ok(Self::NorthAyrshire), + "North Down Borough Council" => Ok(Self::NorthDownBoroughCouncil), + "North East Lincolnshire" => Ok(Self::NorthEastLincolnshire), + "North Lanarkshire" => Ok(Self::NorthLanarkshire), + "North Lincolnshire" => Ok(Self::NorthLincolnshire), + "North Somerset" => Ok(Self::NorthSomerset), + "North Tyneside" => Ok(Self::NorthTyneside), + "North Yorkshire" => Ok(Self::NorthYorkshire), + "Northamptonshire" => Ok(Self::Northamptonshire), + "Northern Ireland" => Ok(Self::NorthernIreland), + "Northumberland" => Ok(Self::Northumberland), + "Nottinghamshire" => Ok(Self::Nottinghamshire), + "Oldham" => Ok(Self::Oldham), + "Omagh District Council" => Ok(Self::OmaghDistrictCouncil), + "Orkney Islands" => Ok(Self::OrkneyIslands), + "Outer Hebrides" => Ok(Self::OuterHebrides), + "Oxfordshire" => Ok(Self::Oxfordshire), + "Pembrokeshire" => Ok(Self::Pembrokeshire), + "Perth and Kinross" => Ok(Self::PerthAndKinross), + "Poole" => Ok(Self::Poole), + "Powys" => Ok(Self::Powys), + "Reading" => Ok(Self::Reading), + "Redcar and Cleveland" => Ok(Self::RedcarAndCleveland), + "Renfrewshire" => Ok(Self::Renfrewshire), + "Rhondda Cynon Taf" => Ok(Self::RhonddaCynonTaf), + "Rochdale" => Ok(Self::Rochdale), + "Rotherham" => Ok(Self::Rotherham), + "Royal Borough of Greenwich" => Ok(Self::RoyalBoroughOfGreenwich), + "Royal Borough of Kensington and Chelsea" => { + Ok(Self::RoyalBoroughOfKensingtonAndChelsea) + } + "Royal Borough of Kingston upon Thames" => { + Ok(Self::RoyalBoroughOfKingstonUponThames) + } + "Rutland" => Ok(Self::Rutland), + "Saint Helena" => Ok(Self::SaintHelena), + "Salford" => Ok(Self::Salford), + "Sandwell" => Ok(Self::Sandwell), + "Scotland" => Ok(Self::Scotland), + "Scottish Borders" => Ok(Self::ScottishBorders), + "Sefton" => Ok(Self::Sefton), + "Sheffield" => Ok(Self::Sheffield), + "Shetland Islands" => Ok(Self::ShetlandIslands), + "Shropshire" => Ok(Self::Shropshire), + "Slough" => Ok(Self::Slough), + "Solihull" => Ok(Self::Solihull), + "Somerset" => Ok(Self::Somerset), + "South Ayrshire" => Ok(Self::SouthAyrshire), + "South Gloucestershire" => Ok(Self::SouthGloucestershire), + "South Lanarkshire" => Ok(Self::SouthLanarkshire), + "South Tyneside" => Ok(Self::SouthTyneside), + "Southend-on-Sea" => Ok(Self::SouthendOnSea), + "St Helens" => Ok(Self::StHelens), + "Staffordshire" => Ok(Self::Staffordshire), + "Stirling" => Ok(Self::Stirling), + "Stockport" => Ok(Self::Stockport), + "Stockton-on-Tees" => Ok(Self::StocktonOnTees), + "Strabane District Council" => Ok(Self::StrabaneDistrictCouncil), + "Suffolk" => Ok(Self::Suffolk), + "Surrey" => Ok(Self::Surrey), + "Swindon" => Ok(Self::Swindon), + "Tameside" => Ok(Self::Tameside), + "Telford and Wrekin" => Ok(Self::TelfordAndWrekin), + "Thurrock" => Ok(Self::Thurrock), + "Torbay" => Ok(Self::Torbay), + "Torfaen" => Ok(Self::Torfaen), + "Trafford" => Ok(Self::Trafford), + "United Kingdom" => Ok(Self::UnitedKingdom), + "Vale of Glamorgan" => Ok(Self::ValeOfGlamorgan), + "Wakefield" => Ok(Self::Wakefield), + "Wales" => Ok(Self::Wales), + "Walsall" => Ok(Self::Walsall), + "Warrington" => Ok(Self::Warrington), + "Warwickshire" => Ok(Self::Warwickshire), + "West Berkshire" => Ok(Self::WestBerkshire), + "West Dunbartonshire" => Ok(Self::WestDunbartonshire), + "West Lothian" => Ok(Self::WestLothian), + "West Sussex" => Ok(Self::WestSussex), + "Wiltshire" => Ok(Self::Wiltshire), + "Windsor and Maidenhead" => Ok(Self::WindsorAndMaidenhead), + "Wirral" => Ok(Self::Wirral), + "Wokingham" => Ok(Self::Wokingham), + "Worcestershire" => Ok(Self::Worcestershire), + "Wrexham County Borough" => Ok(Self::WrexhamCountyBorough), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -4410,35 +4404,29 @@ impl ForeignTryFrom for UnitedKingdomStatesAbbreviation { impl ForeignTryFrom for BelgiumStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "BelgiumStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "BelgiumStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "antwerp" => Ok(Self::Antwerp), - "brussels capital region" => Ok(Self::BrusselsCapitalRegion), - "east flanders" => Ok(Self::EastFlanders), - "flanders" => Ok(Self::Flanders), - "flemish brabant" => Ok(Self::FlemishBrabant), - "hainaut" => Ok(Self::Hainaut), - "limburg" => Ok(Self::Limburg), - "liege" => Ok(Self::Liege), - "luxembourg" => Ok(Self::Luxembourg), - "namur" => Ok(Self::Namur), - "wallonia" => Ok(Self::Wallonia), - "walloon brabant" => Ok(Self::WalloonBrabant), - "west flanders" => Ok(Self::WestFlanders), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Antwerp" => Ok(Self::Antwerp), + "Brussels-Capital Region" => Ok(Self::BrusselsCapitalRegion), + "East Flanders" => Ok(Self::EastFlanders), + "Flanders" => Ok(Self::Flanders), + "Flemish Brabant" => Ok(Self::FlemishBrabant), + "Hainaut" => Ok(Self::Hainaut), + "Limburg" => Ok(Self::Limburg), + "Liège" => Ok(Self::Liege), + "Luxembourg" => Ok(Self::Luxembourg), + "Namur" => Ok(Self::Namur), + "Wallonia" => Ok(Self::Wallonia), + "Walloon Brabant" => Ok(Self::WalloonBrabant), + "West Flanders" => Ok(Self::WestFlanders), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4446,37 +4434,31 @@ impl ForeignTryFrom for BelgiumStatesAbbreviation { impl ForeignTryFrom for LuxembourgStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "LuxembourgStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "LuxembourgStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "canton of capellen" => Ok(Self::CantonOfCapellen), - "canton of clervaux" => Ok(Self::CantonOfClervaux), - "canton of diekirch" => Ok(Self::CantonOfDiekirch), - "canton of echternach" => Ok(Self::CantonOfEchternach), - "canton of esch sur alzette" => Ok(Self::CantonOfEschSurAlzette), - "canton of grevenmacher" => Ok(Self::CantonOfGrevenmacher), - "canton of luxembourg" => Ok(Self::CantonOfLuxembourg), - "canton of mersch" => Ok(Self::CantonOfMersch), - "canton of redange" => Ok(Self::CantonOfRedange), - "canton of remich" => Ok(Self::CantonOfRemich), - "canton of vianden" => Ok(Self::CantonOfVianden), - "canton of wiltz" => Ok(Self::CantonOfWiltz), - "diekirch district" => Ok(Self::DiekirchDistrict), - "grevenmacher district" => Ok(Self::GrevenmacherDistrict), - "luxembourg district" => Ok(Self::LuxembourgDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Canton of Capellen" => Ok(Self::CantonOfCapellen), + "Canton of Clervaux" => Ok(Self::CantonOfClervaux), + "Canton of Diekirch" => Ok(Self::CantonOfDiekirch), + "Canton of Echternach" => Ok(Self::CantonOfEchternach), + "Canton of Esch-sur-Alzette" => Ok(Self::CantonOfEschSurAlzette), + "Canton of Grevenmacher" => Ok(Self::CantonOfGrevenmacher), + "Canton of Luxembourg" => Ok(Self::CantonOfLuxembourg), + "Canton of Mersch" => Ok(Self::CantonOfMersch), + "Canton of Redange" => Ok(Self::CantonOfRedange), + "Canton of Remich" => Ok(Self::CantonOfRemich), + "Canton of Vianden" => Ok(Self::CantonOfVianden), + "Canton of Wiltz" => Ok(Self::CantonOfWiltz), + "Diekirch District" => Ok(Self::DiekirchDistrict), + "Grevenmacher District" => Ok(Self::GrevenmacherDistrict), + "Luxembourg District" => Ok(Self::LuxembourgDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4485,102 +4467,98 @@ impl ForeignTryFrom for RussiaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "RussiaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "RussiaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "altai krai" => Ok(Self::AltaiKrai), - "altai republic" => Ok(Self::AltaiRepublic), - "amur oblast" => Ok(Self::AmurOblast), - "arkhangelsk" => Ok(Self::Arkhangelsk), - "astrakhan oblast" => Ok(Self::AstrakhanOblast), - "belgorod oblast" => Ok(Self::BelgorodOblast), - "bryansk oblast" => Ok(Self::BryanskOblast), - "chechen republic" => Ok(Self::ChechenRepublic), - "chelyabinsk oblast" => Ok(Self::ChelyabinskOblast), - "chukotka autonomous okrug" => Ok(Self::ChukotkaAutonomousOkrug), - "chuvash republic" => Ok(Self::ChuvashRepublic), - "irkutsk" => Ok(Self::Irkutsk), - "ivanovo oblast" => Ok(Self::IvanovoOblast), - "jewish autonomous oblast" => Ok(Self::JewishAutonomousOblast), - "kabardino-balkar republic" => Ok(Self::KabardinoBalkarRepublic), - "kaliningrad" => Ok(Self::Kaliningrad), - "kaluga oblast" => Ok(Self::KalugaOblast), - "kamchatka krai" => Ok(Self::KamchatkaKrai), - "karachay-cherkess republic" => Ok(Self::KarachayCherkessRepublic), - "kemerovo oblast" => Ok(Self::KemerovoOblast), - "khabarovsk krai" => Ok(Self::KhabarovskKrai), - "khanty-mansi autonomous okrug" => Ok(Self::KhantyMansiAutonomousOkrug), - "kirov oblast" => Ok(Self::KirovOblast), - "komi republic" => Ok(Self::KomiRepublic), - "kostroma oblast" => Ok(Self::KostromaOblast), - "krasnodar krai" => Ok(Self::KrasnodarKrai), - "krasnoyarsk krai" => Ok(Self::KrasnoyarskKrai), - "kurgan oblast" => Ok(Self::KurganOblast), - "kursk oblast" => Ok(Self::KurskOblast), - "leningrad oblast" => Ok(Self::LeningradOblast), - "lipetsk oblast" => Ok(Self::LipetskOblast), - "magadan oblast" => Ok(Self::MagadanOblast), - "mari el republic" => Ok(Self::MariElRepublic), - "moscow" => Ok(Self::Moscow), - "moscow oblast" => Ok(Self::MoscowOblast), - "murmansk oblast" => Ok(Self::MurmanskOblast), - "nenets autonomous okrug" => Ok(Self::NenetsAutonomousOkrug), - "nizhny novgorod oblast" => Ok(Self::NizhnyNovgorodOblast), - "novgorod oblast" => Ok(Self::NovgorodOblast), - "novosibirsk" => Ok(Self::Novosibirsk), - "omsk oblast" => Ok(Self::OmskOblast), - "orenburg oblast" => Ok(Self::OrenburgOblast), - "oryol oblast" => Ok(Self::OryolOblast), - "penza oblast" => Ok(Self::PenzaOblast), - "perm krai" => Ok(Self::PermKrai), - "primorsky krai" => Ok(Self::PrimorskyKrai), - "pskov oblast" => Ok(Self::PskovOblast), - "republic of adygea" => Ok(Self::RepublicOfAdygea), - "republic of bashkortostan" => Ok(Self::RepublicOfBashkortostan), - "republic of buryatia" => Ok(Self::RepublicOfBuryatia), - "republic of dagestan" => Ok(Self::RepublicOfDagestan), - "republic of ingushetia" => Ok(Self::RepublicOfIngushetia), - "republic of kalmykia" => Ok(Self::RepublicOfKalmykia), - "republic of karelia" => Ok(Self::RepublicOfKarelia), - "republic of khakassia" => Ok(Self::RepublicOfKhakassia), - "republic of mordovia" => Ok(Self::RepublicOfMordovia), - "republic of north ossetia-alania" => Ok(Self::RepublicOfNorthOssetiaAlania), - "republic of tatarstan" => Ok(Self::RepublicOfTatarstan), - "rostov oblast" => Ok(Self::RostovOblast), - "ryazan oblast" => Ok(Self::RyazanOblast), - "saint petersburg" => Ok(Self::SaintPetersburg), - "sakha republic" => Ok(Self::SakhaRepublic), - "sakhalin" => Ok(Self::Sakhalin), - "samara oblast" => Ok(Self::SamaraOblast), - "saratov oblast" => Ok(Self::SaratovOblast), - "sevastopol" => Ok(Self::Sevastopol), - "smolensk oblast" => Ok(Self::SmolenskOblast), - "stavropol krai" => Ok(Self::StavropolKrai), - "sverdlovsk" => Ok(Self::Sverdlovsk), - "tambov oblast" => Ok(Self::TambovOblast), - "tomsk oblast" => Ok(Self::TomskOblast), - "tula oblast" => Ok(Self::TulaOblast), - "tuva republic" => Ok(Self::TuvaRepublic), - "tver oblast" => Ok(Self::TverOblast), - "tyumen oblast" => Ok(Self::TyumenOblast), - "udmurt republic" => Ok(Self::UdmurtRepublic), - "ulyanovsk oblast" => Ok(Self::UlyanovskOblast), - "vladimir oblast" => Ok(Self::VladimirOblast), - "vologda oblast" => Ok(Self::VologdaOblast), - "voronezh oblast" => Ok(Self::VoronezhOblast), - "yamalo-nenets autonomous okrug" => Ok(Self::YamaloNenetsAutonomousOkrug), - "yaroslavl oblast" => Ok(Self::YaroslavlOblast), - "zabaykalsky krai" => Ok(Self::ZabaykalskyKrai), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Altai Krai" => Ok(Self::AltaiKrai), + "Altai Republic" => Ok(Self::AltaiRepublic), + "Amur Oblast" => Ok(Self::AmurOblast), + "Arkhangelsk" => Ok(Self::Arkhangelsk), + "Astrakhan Oblast" => Ok(Self::AstrakhanOblast), + "Belgorod Oblast" => Ok(Self::BelgorodOblast), + "Bryansk Oblast" => Ok(Self::BryanskOblast), + "Chechen Republic" => Ok(Self::ChechenRepublic), + "Chelyabinsk Oblast" => Ok(Self::ChelyabinskOblast), + "Chukotka Autonomous Okrug" => Ok(Self::ChukotkaAutonomousOkrug), + "Chuvash Republic" => Ok(Self::ChuvashRepublic), + "Irkutsk" => Ok(Self::Irkutsk), + "Ivanovo Oblast" => Ok(Self::IvanovoOblast), + "Jewish Autonomous Oblast" => Ok(Self::JewishAutonomousOblast), + "Kabardino-Balkar Republic" => Ok(Self::KabardinoBalkarRepublic), + "Kaliningrad" => Ok(Self::Kaliningrad), + "Kaluga Oblast" => Ok(Self::KalugaOblast), + "Kamchatka Krai" => Ok(Self::KamchatkaKrai), + "Karachay-Cherkess Republic" => Ok(Self::KarachayCherkessRepublic), + "Kemerovo Oblast" => Ok(Self::KemerovoOblast), + "Khabarovsk Krai" => Ok(Self::KhabarovskKrai), + "Khanty-Mansi Autonomous Okrug" => Ok(Self::KhantyMansiAutonomousOkrug), + "Kirov Oblast" => Ok(Self::KirovOblast), + "Komi Republic" => Ok(Self::KomiRepublic), + "Kostroma Oblast" => Ok(Self::KostromaOblast), + "Krasnodar Krai" => Ok(Self::KrasnodarKrai), + "Krasnoyarsk Krai" => Ok(Self::KrasnoyarskKrai), + "Kurgan Oblast" => Ok(Self::KurganOblast), + "Kursk Oblast" => Ok(Self::KurskOblast), + "Leningrad Oblast" => Ok(Self::LeningradOblast), + "Lipetsk Oblast" => Ok(Self::LipetskOblast), + "Magadan Oblast" => Ok(Self::MagadanOblast), + "Mari El Republic" => Ok(Self::MariElRepublic), + "Moscow" => Ok(Self::Moscow), + "Moscow Oblast" => Ok(Self::MoscowOblast), + "Murmansk Oblast" => Ok(Self::MurmanskOblast), + "Nenets Autonomous Okrug" => Ok(Self::NenetsAutonomousOkrug), + "Nizhny Novgorod Oblast" => Ok(Self::NizhnyNovgorodOblast), + "Novgorod Oblast" => Ok(Self::NovgorodOblast), + "Novosibirsk" => Ok(Self::Novosibirsk), + "Omsk Oblast" => Ok(Self::OmskOblast), + "Orenburg Oblast" => Ok(Self::OrenburgOblast), + "Oryol Oblast" => Ok(Self::OryolOblast), + "Penza Oblast" => Ok(Self::PenzaOblast), + "Perm Krai" => Ok(Self::PermKrai), + "Primorsky Krai" => Ok(Self::PrimorskyKrai), + "Pskov Oblast" => Ok(Self::PskovOblast), + "Republic of Adygea" => Ok(Self::RepublicOfAdygea), + "Republic of Bashkortostan" => Ok(Self::RepublicOfBashkortostan), + "Republic of Buryatia" => Ok(Self::RepublicOfBuryatia), + "Republic of Dagestan" => Ok(Self::RepublicOfDagestan), + "Republic of Ingushetia" => Ok(Self::RepublicOfIngushetia), + "Republic of Kalmykia" => Ok(Self::RepublicOfKalmykia), + "Republic of Karelia" => Ok(Self::RepublicOfKarelia), + "Republic of Khakassia" => Ok(Self::RepublicOfKhakassia), + "Republic of Mordovia" => Ok(Self::RepublicOfMordovia), + "Republic of North Ossetia-Alania" => Ok(Self::RepublicOfNorthOssetiaAlania), + "Republic of Tatarstan" => Ok(Self::RepublicOfTatarstan), + "Rostov Oblast" => Ok(Self::RostovOblast), + "Ryazan Oblast" => Ok(Self::RyazanOblast), + "Saint Petersburg" => Ok(Self::SaintPetersburg), + "Sakha Republic" => Ok(Self::SakhaRepublic), + "Sakhalin" => Ok(Self::Sakhalin), + "Samara Oblast" => Ok(Self::SamaraOblast), + "Saratov Oblast" => Ok(Self::SaratovOblast), + "Sevastopol" => Ok(Self::Sevastopol), + "Smolensk Oblast" => Ok(Self::SmolenskOblast), + "Stavropol Krai" => Ok(Self::StavropolKrai), + "Sverdlovsk" => Ok(Self::Sverdlovsk), + "Tambov Oblast" => Ok(Self::TambovOblast), + "Tomsk Oblast" => Ok(Self::TomskOblast), + "Tula Oblast" => Ok(Self::TulaOblast), + "Tuva Republic" => Ok(Self::TuvaRepublic), + "Tver Oblast" => Ok(Self::TverOblast), + "Tyumen Oblast" => Ok(Self::TyumenOblast), + "Udmurt Republic" => Ok(Self::UdmurtRepublic), + "Ulyanovsk Oblast" => Ok(Self::UlyanovskOblast), + "Vladimir Oblast" => Ok(Self::VladimirOblast), + "Vologda Oblast" => Ok(Self::VologdaOblast), + "Voronezh Oblast" => Ok(Self::VoronezhOblast), + "Yamalo-Nenets Autonomous Okrug" => Ok(Self::YamaloNenetsAutonomousOkrug), + "Yaroslavl Oblast" => Ok(Self::YaroslavlOblast), + "Zabaykalsky Krai" => Ok(Self::ZabaykalskyKrai), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4588,31 +4566,25 @@ impl ForeignTryFrom for RussiaStatesAbbreviation { impl ForeignTryFrom for SanMarinoStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "SanMarinoStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SanMarinoStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "acquaviva" => Ok(Self::Acquaviva), - "borgo maggiore" => Ok(Self::BorgoMaggiore), - "chiesanuova" => Ok(Self::Chiesanuova), - "domagnano" => Ok(Self::Domagnano), - "faetano" => Ok(Self::Faetano), - "fiorentino" => Ok(Self::Fiorentino), - "montegiardino" => Ok(Self::Montegiardino), - "san marino" => Ok(Self::SanMarino), - "serravalle" => Ok(Self::Serravalle), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Acquaviva" => Ok(Self::Acquaviva), + "Borgo Maggiore" => Ok(Self::BorgoMaggiore), + "Chiesanuova" => Ok(Self::Chiesanuova), + "Domagnano" => Ok(Self::Domagnano), + "Faetano" => Ok(Self::Faetano), + "Fiorentino" => Ok(Self::Fiorentino), + "Montegiardino" => Ok(Self::Montegiardino), + "San Marino" => Ok(Self::SanMarino), + "Serravalle" => Ok(Self::Serravalle), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4621,45 +4593,41 @@ impl ForeignTryFrom for SerbiaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "SerbiaStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "SerbiaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "belgrade" => Ok(Self::Belgrade), - "bor district" => Ok(Self::BorDistrict), - "braničevo district" => Ok(Self::BraničevoDistrict), - "central banat district" => Ok(Self::CentralBanatDistrict), - "jablanica district" => Ok(Self::JablanicaDistrict), - "kolubara district" => Ok(Self::KolubaraDistrict), - "mačva district" => Ok(Self::MačvaDistrict), - "moravica district" => Ok(Self::MoravicaDistrict), - "nišava district" => Ok(Self::NišavaDistrict), - "north banat district" => Ok(Self::NorthBanatDistrict), - "north bačka district" => Ok(Self::NorthBačkaDistrict), - "pirot district" => Ok(Self::PirotDistrict), - "podunavlje district" => Ok(Self::PodunavljeDistrict), - "pomoravlje district" => Ok(Self::PomoravljeDistrict), - "pčinja district" => Ok(Self::PčinjaDistrict), - "rasina district" => Ok(Self::RasinaDistrict), - "raška district" => Ok(Self::RaškaDistrict), - "south banat district" => Ok(Self::SouthBanatDistrict), - "south bačka district" => Ok(Self::SouthBačkaDistrict), - "srem district" => Ok(Self::SremDistrict), - "toplica district" => Ok(Self::ToplicaDistrict), - "vojvodina" => Ok(Self::Vojvodina), - "west bačka district" => Ok(Self::WestBačkaDistrict), - "zaječar district" => Ok(Self::ZaječarDistrict), - "zlatibor district" => Ok(Self::ZlatiborDistrict), - "šumadija district" => Ok(Self::ŠumadijaDistrict), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Belgrade" => Ok(Self::Belgrade), + "Bor District" => Ok(Self::BorDistrict), + "Braničevo District" => Ok(Self::BraničevoDistrict), + "Central Banat District" => Ok(Self::CentralBanatDistrict), + "Jablanica District" => Ok(Self::JablanicaDistrict), + "Kolubara District" => Ok(Self::KolubaraDistrict), + "Mačva District" => Ok(Self::MačvaDistrict), + "Moravica District" => Ok(Self::MoravicaDistrict), + "Nišava District" => Ok(Self::NišavaDistrict), + "North Banat District" => Ok(Self::NorthBanatDistrict), + "North Bačka District" => Ok(Self::NorthBačkaDistrict), + "Pirot District" => Ok(Self::PirotDistrict), + "Podunavlje District" => Ok(Self::PodunavljeDistrict), + "Pomoravlje District" => Ok(Self::PomoravljeDistrict), + "Pčinja District" => Ok(Self::PčinjaDistrict), + "Rasina District" => Ok(Self::RasinaDistrict), + "Raška District" => Ok(Self::RaškaDistrict), + "South Banat District" => Ok(Self::SouthBanatDistrict), + "South Bačka District" => Ok(Self::SouthBačkaDistrict), + "Srem District" => Ok(Self::SremDistrict), + "Toplica District" => Ok(Self::ToplicaDistrict), + "Vojvodina" => Ok(Self::Vojvodina), + "West Bačka District" => Ok(Self::WestBačkaDistrict), + "Zaječar District" => Ok(Self::ZaječarDistrict), + "Zlatibor District" => Ok(Self::ZlatiborDistrict), + "Šumadija District" => Ok(Self::ŠumadijaDistrict), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4667,30 +4635,24 @@ impl ForeignTryFrom for SerbiaStatesAbbreviation { impl ForeignTryFrom for SlovakiaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "SlovakiaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SlovakiaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "banska bystrica region" => Ok(Self::BanskaBystricaRegion), - "bratislava region" => Ok(Self::BratislavaRegion), - "kosice region" => Ok(Self::KosiceRegion), - "nitra region" => Ok(Self::NitraRegion), - "presov region" => Ok(Self::PresovRegion), - "trencin region" => Ok(Self::TrencinRegion), - "trnava region" => Ok(Self::TrnavaRegion), - "zilina region" => Ok(Self::ZilinaRegion), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Banská Bystrica Region" => Ok(Self::BanskaBystricaRegion), + "Bratislava Region" => Ok(Self::BratislavaRegion), + "Košice Region" => Ok(Self::KosiceRegion), + "Nitra Region" => Ok(Self::NitraRegion), + "Prešov Region" => Ok(Self::PresovRegion), + "Trenčín Region" => Ok(Self::TrencinRegion), + "Trnava Region" => Ok(Self::TrnavaRegion), + "Žilina Region" => Ok(Self::ZilinaRegion), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4699,39 +4661,35 @@ impl ForeignTryFrom for SwedenStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { let state_abbreviation_check = - StringExt::::parse_enum(value.to_uppercase().clone(), "SwedenStatesAbbreviation"); + StringExt::::parse_enum(value.clone(), "SwedenStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "blekinge" => Ok(Self::Blekinge), - "dalarna county" => Ok(Self::DalarnaCounty), - "gotland county" => Ok(Self::GotlandCounty), - "gävleborg county" => Ok(Self::GävleborgCounty), - "halland county" => Ok(Self::HallandCounty), - "jönköping county" => Ok(Self::JönköpingCounty), - "kalmar county" => Ok(Self::KalmarCounty), - "kronoberg county" => Ok(Self::KronobergCounty), - "norrbotten county" => Ok(Self::NorrbottenCounty), - "skåne county" => Ok(Self::SkåneCounty), - "stockholm county" => Ok(Self::StockholmCounty), - "södermanland county" => Ok(Self::SödermanlandCounty), - "uppsala county" => Ok(Self::UppsalaCounty), - "värmland county" => Ok(Self::VärmlandCounty), - "västerbotten county" => Ok(Self::VästerbottenCounty), - "västernorrland county" => Ok(Self::VästernorrlandCounty), - "västmanland county" => Ok(Self::VästmanlandCounty), - "västra götaland county" => Ok(Self::VästraGötalandCounty), - "örebro county" => Ok(Self::ÖrebroCounty), - "östergötland county" => Ok(Self::ÖstergötlandCounty), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Blekinge" => Ok(Self::Blekinge), + "Dalarna County" => Ok(Self::DalarnaCounty), + "Gotland County" => Ok(Self::GotlandCounty), + "Gävleborg County" => Ok(Self::GävleborgCounty), + "Halland County" => Ok(Self::HallandCounty), + "Jönköping County" => Ok(Self::JönköpingCounty), + "Kalmar County" => Ok(Self::KalmarCounty), + "Kronoberg County" => Ok(Self::KronobergCounty), + "Norrbotten County" => Ok(Self::NorrbottenCounty), + "Skåne County" => Ok(Self::SkåneCounty), + "Stockholm County" => Ok(Self::StockholmCounty), + "Södermanland County" => Ok(Self::SödermanlandCounty), + "Uppsala County" => Ok(Self::UppsalaCounty), + "Värmland County" => Ok(Self::VärmlandCounty), + "Västerbotten County" => Ok(Self::VästerbottenCounty), + "Västernorrland County" => Ok(Self::VästernorrlandCounty), + "Västmanland County" => Ok(Self::VästmanlandCounty), + "Västra Götaland County" => Ok(Self::VästraGötalandCounty), + "Örebro County" => Ok(Self::ÖrebroCounty), + "Östergötland County" => Ok(Self::ÖstergötlandCounty), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } @@ -4739,76 +4697,229 @@ impl ForeignTryFrom for SwedenStatesAbbreviation { impl ForeignTryFrom for SloveniaStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "SloveniaStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "SloveniaStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - match state { - "ajdovščina" => Ok(Self::Ajdovščina), - "ankaran" => Ok(Self::Ankaran), - "beltinci" => Ok(Self::Beltinci), - "benedikt" => Ok(Self::Benedikt), - "bistrica ob sotli" => Ok(Self::BistricaObSotli), - "bled" => Ok(Self::Bled), - "bloke" => Ok(Self::Bloke), - "bohinj" => Ok(Self::Bohinj), - "borovnica" => Ok(Self::Borovnica), - "bovec" => Ok(Self::Bovec), - "braslovče" => Ok(Self::Braslovče), - "brda" => Ok(Self::Brda), - "brezovica" => Ok(Self::Brezovica), - "brežice" => Ok(Self::Brežice), - "cankova" => Ok(Self::Cankova), - "cerklje na gorenjskem" => Ok(Self::CerkljeNaGorenjskem), - "cerknica" => Ok(Self::Cerknica), - "cerkno" => Ok(Self::Cerkno), - "cerkvenjak" => Ok(Self::Cerkvenjak), - "city municipality of celje" => Ok(Self::CityMunicipalityOfCelje), - "city municipality of novo mesto" => Ok(Self::CityMunicipalityOfNovoMesto), - "destrnik" => Ok(Self::Destrnik), - "divača" => Ok(Self::Divača), - "dobje" => Ok(Self::Dobje), - "dobrepolje" => Ok(Self::Dobrepolje), - "dobrna" => Ok(Self::Dobrna), - "dobrova-polhov gradec" => Ok(Self::DobrovaPolhovGradec), - "dobrovnik" => Ok(Self::Dobrovnik), - "dol pri ljubljani" => Ok(Self::DolPriLjubljani), - "dolenjske toplice" => Ok(Self::DolenjskeToplice), - "domžale" => Ok(Self::Domžale), - "dornava" => Ok(Self::Dornava), - "dravograd" => Ok(Self::Dravograd), - "duplek" => Ok(Self::Duplek), - "gorenja vas-poljane" => Ok(Self::GorenjaVasPoljane), - "gorišnica" => Ok(Self::Gorišnica), - "gorje" => Ok(Self::Gorje), - "gornja radgona" => Ok(Self::GornjaRadgona), - "gornji grad" => Ok(Self::GornjiGrad), - "gornji petrovci" => Ok(Self::GornjiPetrovci), - "grad" => Ok(Self::Grad), - "grosuplje" => Ok(Self::Grosuplje), - "hajdina" => Ok(Self::Hajdina), - "hodoš" => Ok(Self::Hodoš), - "horjul" => Ok(Self::Horjul), - "hoče-slivnica" => Ok(Self::HočeSlivnica), - "hrastnik" => Ok(Self::Hrastnik), - "hrpelje-kozina" => Ok(Self::HrpeljeKozina), - "idrija" => Ok(Self::Idrija), - "ig" => Ok(Self::Ig), - "ivančna gorica" => Ok(Self::IvančnaGorica), - "izola" => Ok(Self::Izola), - "jesenice" => Ok(Self::Jesenice), - "jezersko" => Ok(Self::Jezersko), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Ajdovščina Municipality" => Ok(Self::Ajdovščina), + "Ankaran Municipality" => Ok(Self::Ankaran), + "Beltinci Municipality" => Ok(Self::Beltinci), + "Benedikt Municipality" => Ok(Self::Benedikt), + "Bistrica ob Sotli Municipality" => Ok(Self::BistricaObSotli), + "Bled Municipality" => Ok(Self::Bled), + "Bloke Municipality" => Ok(Self::Bloke), + "Bohinj Municipality" => Ok(Self::Bohinj), + "Borovnica Municipality" => Ok(Self::Borovnica), + "Bovec Municipality" => Ok(Self::Bovec), + "Braslovče Municipality" => Ok(Self::Braslovče), + "Brda Municipality" => Ok(Self::Brda), + "Brezovica Municipality" => Ok(Self::Brezovica), + "Brežice Municipality" => Ok(Self::Brežice), + "Cankova Municipality" => Ok(Self::Cankova), + "Cerklje na Gorenjskem Municipality" => Ok(Self::CerkljeNaGorenjskem), + "Cerknica Municipality" => Ok(Self::Cerknica), + "Cerkno Municipality" => Ok(Self::Cerkno), + "Cerkvenjak Municipality" => Ok(Self::Cerkvenjak), + "City Municipality of Celje" => Ok(Self::CityMunicipalityOfCelje), + "City Municipality of Novo Mesto" => Ok(Self::CityMunicipalityOfNovoMesto), + "Destrnik Municipality" => Ok(Self::Destrnik), + "Divača Municipality" => Ok(Self::Divača), + "Dobje Municipality" => Ok(Self::Dobje), + "Dobrepolje Municipality" => Ok(Self::Dobrepolje), + "Dobrna Municipality" => Ok(Self::Dobrna), + "Dobrova–Polhov Gradec Municipality" => Ok(Self::DobrovaPolhovGradec), + "Dobrovnik Municipality" => Ok(Self::Dobrovnik), + "Dol pri Ljubljani Municipality" => Ok(Self::DolPriLjubljani), + "Dolenjske Toplice Municipality" => Ok(Self::DolenjskeToplice), + "Domžale Municipality" => Ok(Self::Domžale), + "Dornava Municipality" => Ok(Self::Dornava), + "Dravograd Municipality" => Ok(Self::Dravograd), + "Duplek Municipality" => Ok(Self::Duplek), + "Gorenja Vas–Poljane Municipality" => Ok(Self::GorenjaVasPoljane), + "Gorišnica Municipality" => Ok(Self::Gorišnica), + "Gorje Municipality" => Ok(Self::Gorje), + "Gornja Radgona Municipality" => Ok(Self::GornjaRadgona), + "Gornji Grad Municipality" => Ok(Self::GornjiGrad), + "Gornji Petrovci Municipality" => Ok(Self::GornjiPetrovci), + "Grad Municipality" => Ok(Self::Grad), + "Grosuplje Municipality" => Ok(Self::Grosuplje), + "Hajdina Municipality" => Ok(Self::Hajdina), + "Hodoš Municipality" => Ok(Self::Hodoš), + "Horjul Municipality" => Ok(Self::Horjul), + "Hoče–Slivnica Municipality" => Ok(Self::HočeSlivnica), + "Hrastnik Municipality" => Ok(Self::Hrastnik), + "Hrpelje–Kozina Municipality" => Ok(Self::HrpeljeKozina), + "Idrija Municipality" => Ok(Self::Idrija), + "Ig Municipality" => Ok(Self::Ig), + "Ivančna Gorica Municipality" => Ok(Self::IvančnaGorica), + "Izola Municipality" => Ok(Self::Izola), + "Jesenice Municipality" => Ok(Self::Jesenice), + "Jezersko Municipality" => Ok(Self::Jezersko), + "Juršinci Municipality" => Ok(Self::Jursinci), + "Kamnik Municipality" => Ok(Self::Kamnik), + "Kanal ob Soči Municipality" => Ok(Self::KanalObSoci), + "Kidričevo Municipality" => Ok(Self::Kidricevo), + "Kobarid Municipality" => Ok(Self::Kobarid), + "Kobilje Municipality" => Ok(Self::Kobilje), + "Komen Municipality" => Ok(Self::Komen), + "Komenda Municipality" => Ok(Self::Komenda), + "Koper City Municipality" => Ok(Self::Koper), + "Kostanjevica na Krki Municipality" => Ok(Self::KostanjevicaNaKrki), + "Kostel Municipality" => Ok(Self::Kostel), + "Kozje Municipality" => Ok(Self::Kozje), + "Kočevje Municipality" => Ok(Self::Kocevje), + "Kranj City Municipality" => Ok(Self::Kranj), + "Kranjska Gora Municipality" => Ok(Self::KranjskaGora), + "Križevci Municipality" => Ok(Self::Krizevci), + "Kungota" => Ok(Self::Kungota), + "Kuzma Municipality" => Ok(Self::Kuzma), + "Laško Municipality" => Ok(Self::Lasko), + "Lenart Municipality" => Ok(Self::Lenart), + "Lendava Municipality" => Ok(Self::Lendava), + "Litija Municipality" => Ok(Self::Litija), + "Ljubljana City Municipality" => Ok(Self::Ljubljana), + "Ljubno Municipality" => Ok(Self::Ljubno), + "Ljutomer Municipality" => Ok(Self::Ljutomer), + "Logatec Municipality" => Ok(Self::Logatec), + "Log–Dragomer Municipality" => Ok(Self::LogDragomer), + "Lovrenc na Pohorju Municipality" => Ok(Self::LovrencNaPohorju), + "Loška Dolina Municipality" => Ok(Self::LoskaDolina), + "Loški Potok Municipality" => Ok(Self::LoskiPotok), + "Lukovica Municipality" => Ok(Self::Lukovica), + "Luče Municipality" => Ok(Self::Luče), + "Majšperk Municipality" => Ok(Self::Majsperk), + "Makole Municipality" => Ok(Self::Makole), + "Maribor City Municipality" => Ok(Self::Maribor), + "Markovci Municipality" => Ok(Self::Markovci), + "Medvode Municipality" => Ok(Self::Medvode), + "Mengeš Municipality" => Ok(Self::Menges), + "Metlika Municipality" => Ok(Self::Metlika), + "Mežica Municipality" => Ok(Self::Mezica), + "Miklavž na Dravskem Polju Municipality" => Ok(Self::MiklavzNaDravskemPolju), + "Miren–Kostanjevica Municipality" => Ok(Self::MirenKostanjevica), + "Mirna Municipality" => Ok(Self::Mirna), + "Mirna Peč Municipality" => Ok(Self::MirnaPec), + "Mislinja Municipality" => Ok(Self::Mislinja), + "Mokronog–Trebelno Municipality" => Ok(Self::MokronogTrebelno), + "Moravske Toplice Municipality" => Ok(Self::MoravskeToplice), + "Moravče Municipality" => Ok(Self::Moravce), + "Mozirje Municipality" => Ok(Self::Mozirje), + "Municipality of Apače" => Ok(Self::Apače), + "Municipality of Cirkulane" => Ok(Self::Cirkulane), + "Municipality of Ilirska Bistrica" => Ok(Self::IlirskaBistrica), + "Municipality of Krško" => Ok(Self::Krsko), + "Municipality of Škofljica" => Ok(Self::Skofljica), + "Murska Sobota City Municipality" => Ok(Self::MurskaSobota), + "Muta Municipality" => Ok(Self::Muta), + "Naklo Municipality" => Ok(Self::Naklo), + "Nazarje Municipality" => Ok(Self::Nazarje), + "Nova Gorica City Municipality" => Ok(Self::NovaGorica), + "Odranci Municipality" => Ok(Self::Odranci), + "Oplotnica" => Ok(Self::Oplotnica), + "Ormož Municipality" => Ok(Self::Ormoz), + "Osilnica Municipality" => Ok(Self::Osilnica), + "Pesnica Municipality" => Ok(Self::Pesnica), + "Piran Municipality" => Ok(Self::Piran), + "Pivka Municipality" => Ok(Self::Pivka), + "Podlehnik Municipality" => Ok(Self::Podlehnik), + "Podvelka Municipality" => Ok(Self::Podvelka), + "Podčetrtek Municipality" => Ok(Self::Podcetrtek), + "Poljčane Municipality" => Ok(Self::Poljcane), + "Polzela Municipality" => Ok(Self::Polzela), + "Postojna Municipality" => Ok(Self::Postojna), + "Prebold Municipality" => Ok(Self::Prebold), + "Preddvor Municipality" => Ok(Self::Preddvor), + "Prevalje Municipality" => Ok(Self::Prevalje), + "Ptuj City Municipality" => Ok(Self::Ptuj), + "Puconci Municipality" => Ok(Self::Puconci), + "Radenci Municipality" => Ok(Self::Radenci), + "Radeče Municipality" => Ok(Self::Radece), + "Radlje ob Dravi Municipality" => Ok(Self::RadljeObDravi), + "Radovljica Municipality" => Ok(Self::Radovljica), + "Ravne na Koroškem Municipality" => Ok(Self::RavneNaKoroskem), + "Razkrižje Municipality" => Ok(Self::Razkrizje), + "Rače–Fram Municipality" => Ok(Self::RaceFram), + "Renče–Vogrsko Municipality" => Ok(Self::RenčeVogrsko), + "Rečica ob Savinji Municipality" => Ok(Self::RecicaObSavinji), + "Ribnica Municipality" => Ok(Self::Ribnica), + "Ribnica na Pohorju Municipality" => Ok(Self::RibnicaNaPohorju), + "Rogatec Municipality" => Ok(Self::Rogatec), + "Rogaška Slatina Municipality" => Ok(Self::RogaskaSlatina), + "Rogašovci Municipality" => Ok(Self::Rogasovci), + "Ruše Municipality" => Ok(Self::Ruse), + "Selnica ob Dravi Municipality" => Ok(Self::SelnicaObDravi), + "Semič Municipality" => Ok(Self::Semic), + "Sevnica Municipality" => Ok(Self::Sevnica), + "Sežana Municipality" => Ok(Self::Sezana), + "Slovenj Gradec City Municipality" => Ok(Self::SlovenjGradec), + "Slovenska Bistrica Municipality" => Ok(Self::SlovenskaBistrica), + "Slovenske Konjice Municipality" => Ok(Self::SlovenskeKonjice), + "Sodražica Municipality" => Ok(Self::Sodrazica), + "Solčava Municipality" => Ok(Self::Solcava), + "Središče ob Dravi" => Ok(Self::SredisceObDravi), + "Starše Municipality" => Ok(Self::Starse), + "Straža Municipality" => Ok(Self::Straza), + "Sveta Ana Municipality" => Ok(Self::SvetaAna), + "Sveta Trojica v Slovenskih Goricah Municipality" => Ok(Self::SvetaTrojica), + "Sveti Andraž v Slovenskih Goricah Municipality" => Ok(Self::SvetiAndraz), + "Sveti Jurij ob Ščavnici Municipality" => Ok(Self::SvetiJurijObScavnici), + "Sveti Jurij v Slovenskih Goricah Municipality" => { + Ok(Self::SvetiJurijVSlovenskihGoricah) } - } + "Sveti Tomaž Municipality" => Ok(Self::SvetiTomaz), + "Tabor Municipality" => Ok(Self::Tabor), + "Tišina Municipality" => Ok(Self::Tišina), + "Tolmin Municipality" => Ok(Self::Tolmin), + "Trbovlje Municipality" => Ok(Self::Trbovlje), + "Trebnje Municipality" => Ok(Self::Trebnje), + "Trnovska Vas Municipality" => Ok(Self::TrnovskaVas), + "Trzin Municipality" => Ok(Self::Trzin), + "Tržič Municipality" => Ok(Self::Tržič), + "Turnišče Municipality" => Ok(Self::Turnišče), + "Velika Polana Municipality" => Ok(Self::VelikaPolana), + "Velike Lašče Municipality" => Ok(Self::VelikeLašče), + "Veržej Municipality" => Ok(Self::Veržej), + "Videm Municipality" => Ok(Self::Videm), + "Vipava Municipality" => Ok(Self::Vipava), + "Vitanje Municipality" => Ok(Self::Vitanje), + "Vodice Municipality" => Ok(Self::Vodice), + "Vojnik Municipality" => Ok(Self::Vojnik), + "Vransko Municipality" => Ok(Self::Vransko), + "Vrhnika Municipality" => Ok(Self::Vrhnika), + "Vuzenica Municipality" => Ok(Self::Vuzenica), + "Zagorje ob Savi Municipality" => Ok(Self::ZagorjeObSavi), + "Zavrč Municipality" => Ok(Self::Zavrč), + "Zreče Municipality" => Ok(Self::Zreče), + "Črenšovci Municipality" => Ok(Self::Črenšovci), + "Črna na Koroškem Municipality" => Ok(Self::ČrnaNaKoroškem), + "Črnomelj Municipality" => Ok(Self::Črnomelj), + "Šalovci Municipality" => Ok(Self::Šalovci), + "Šempeter–Vrtojba Municipality" => Ok(Self::ŠempeterVrtojba), + "Šentilj Municipality" => Ok(Self::Šentilj), + "Šentjernej Municipality" => Ok(Self::Šentjernej), + "Šentjur Municipality" => Ok(Self::Šentjur), + "Šentrupert Municipality" => Ok(Self::Šentrupert), + "Šenčur Municipality" => Ok(Self::Šenčur), + "Škocjan Municipality" => Ok(Self::Škocjan), + "Škofja Loka Municipality" => Ok(Self::ŠkofjaLoka), + "Šmarje pri Jelšah Municipality" => Ok(Self::ŠmarjePriJelšah), + "Šmarješke Toplice Municipality" => Ok(Self::ŠmarješkeToplice), + "Šmartno ob Paki Municipality" => Ok(Self::ŠmartnoObPaki), + "Šmartno pri Litiji Municipality" => Ok(Self::ŠmartnoPriLitiji), + "Šoštanj Municipality" => Ok(Self::Šoštanj), + "Štore Municipality" => Ok(Self::Štore), + "Žalec Municipality" => Ok(Self::Žalec), + "Železniki Municipality" => Ok(Self::Železniki), + "Žetale Municipality" => Ok(Self::Žetale), + "Žiri Municipality" => Ok(Self::Žiri), + "Žirovnica Municipality" => Ok(Self::Žirovnica), + "Žužemberk Municipality" => Ok(Self::Žužemberk), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", + } + .into()), + }, } } } @@ -4817,49 +4928,42 @@ impl ForeignTryFrom for UkraineStatesAbbreviation { type Error = error_stack::Report; fn foreign_try_from(value: String) -> Result { - let state_abbreviation_check = StringExt::::parse_enum( - value.to_uppercase().clone(), - "UkraineStatesAbbreviation", - ); + let state_abbreviation_check = + StringExt::::parse_enum(value.clone(), "UkraineStatesAbbreviation"); match state_abbreviation_check { Ok(state_abbreviation) => Ok(state_abbreviation), - Err(_) => { - let binding = value.as_str().to_lowercase(); - let state = binding.as_str(); - - match state { - "autonomous republic of crimea" => Ok(Self::AutonomousRepublicOfCrimea), - "cherkasy oblast" => Ok(Self::CherkasyOblast), - "chernihiv oblast" => Ok(Self::ChernihivOblast), - "chernivtsi oblast" => Ok(Self::ChernivtsiOblast), - "dnipropetrovsk oblast" => Ok(Self::DnipropetrovskOblast), - "donetsk oblast" => Ok(Self::DonetskOblast), - "ivano-frankivsk oblast" => Ok(Self::IvanoFrankivskOblast), - "kharkiv oblast" => Ok(Self::KharkivOblast), - "kherson oblast" => Ok(Self::KhersonOblast), - "khmelnytsky oblast" => Ok(Self::KhmelnytskyOblast), - "kiev" => Ok(Self::Kiev), - "kirovohrad oblast" => Ok(Self::KirovohradOblast), - "kyiv oblast" => Ok(Self::KyivOblast), - "luhansk oblast" => Ok(Self::LuhanskOblast), - "lviv oblast" => Ok(Self::LvivOblast), - "mykolaiv oblast" => Ok(Self::MykolaivOblast), - "odessa oblast" => Ok(Self::OdessaOblast), - "rivne oblast" => Ok(Self::RivneOblast), - "sumy oblast" => Ok(Self::SumyOblast), - "ternopil oblast" => Ok(Self::TernopilOblast), - "vinnytsia oblast" => Ok(Self::VinnytsiaOblast), - "volyn oblast" => Ok(Self::VolynOblast), - "zakarpattia oblast" => Ok(Self::ZakarpattiaOblast), - "zaporizhzhya oblast" => Ok(Self::ZaporizhzhyaOblast), - "zhytomyr oblast" => Ok(Self::ZhytomyrOblast), - _ => Err(errors::ConnectorError::InvalidDataFormat { - field_name: "address.state", - } - .into()), + Err(_) => match value.as_str() { + "Autonomous Republic of Crimea" => Ok(Self::AutonomousRepublicOfCrimea), + "Cherkasy Oblast" => Ok(Self::CherkasyOblast), + "Chernihiv Oblast" => Ok(Self::ChernihivOblast), + "Chernivtsi Oblast" => Ok(Self::ChernivtsiOblast), + "Dnipropetrovsk Oblast" => Ok(Self::DnipropetrovskOblast), + "Donetsk Oblast" => Ok(Self::DonetskOblast), + "Ivano-Frankivsk Oblast" => Ok(Self::IvanoFrankivskOblast), + "Kharkiv Oblast" => Ok(Self::KharkivOblast), + "Kherson Oblast" => Ok(Self::KhersonOblast), + "Khmelnytsky Oblast" => Ok(Self::KhmelnytskyOblast), + "Kiev" => Ok(Self::Kiev), + "Kirovohrad Oblast" => Ok(Self::KirovohradOblast), + "Kyiv Oblast" => Ok(Self::KyivOblast), + "Luhansk Oblast" => Ok(Self::LuhanskOblast), + "Lviv Oblast" => Ok(Self::LvivOblast), + "Mykolaiv Oblast" => Ok(Self::MykolaivOblast), + "Odessa Oblast" => Ok(Self::OdessaOblast), + "Rivne Oblast" => Ok(Self::RivneOblast), + "Sumy Oblast" => Ok(Self::SumyOblast), + "Ternopil Oblast" => Ok(Self::TernopilOblast), + "Vinnytsia Oblast" => Ok(Self::VinnytsiaOblast), + "Volyn Oblast" => Ok(Self::VolynOblast), + "Zakarpattia Oblast" => Ok(Self::ZakarpattiaOblast), + "Zaporizhzhya Oblast" => Ok(Self::ZaporizhzhyaOblast), + "Zhytomyr Oblast" => Ok(Self::ZhytomyrOblast), + _ => Err(errors::ConnectorError::InvalidDataFormat { + field_name: "address.state", } - } + .into()), + }, } } } From 8457d3c0b49edcb2ee893853cfb3bd978bc6ae65 Mon Sep 17 00:00:00 2001 From: likhinbopanna <131246334+likhinbopanna@users.noreply.github.com> Date: Wed, 19 Feb 2025 20:05:20 +0530 Subject: [PATCH 115/133] ci(cypress): add assertions for `requires_cvv` (#7296) --- cypress-tests/cypress/support/commands.js | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index fc25220b80b..2257b9ffe54 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1275,6 +1275,7 @@ Cypress.Commands.add( createPaymentBody.profile_id = profile_id; globalState.set("paymentAmount", createPaymentBody.amount); + globalState.set("setupFutureUsage", createPaymentBody.setup_future_usage); cy.request({ method: "POST", @@ -3054,6 +3055,7 @@ Cypress.Commands.add("listCustomerPMCallTest", (globalState) => { Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { const clientSecret = globalState.get("clientSecret"); + const setupFutureUsage = globalState.get("setupFutureUsage"); cy.request({ method: "GET", @@ -3078,6 +3080,18 @@ Cypress.Commands.add("listCustomerPMByClientSecret", (globalState) => { response.body.customer_payment_methods[0].payment_method_id, "payment_method_id" ).to.not.be.null; + + if (setupFutureUsage === "off_session") { + expect( + response.body.customer_payment_methods[0].requires_cvv, + "requires_cvv" + ).to.be.false; + } else if (setupFutureUsage === "on_session") { + expect( + response.body.customer_payment_methods[0].requires_cvv, + "requires_cvv" + ).to.be.true; + } } else { // We only get an empty array if something's wrong. One exception is a 4xx when no customer exist but it is handled in the test expect(response.body) From 2d9df53491b1ef662736efec60ec5e5368466bb4 Mon Sep 17 00:00:00 2001 From: Suman Maji <77887221+sumanmaji4@users.noreply.github.com> Date: Thu, 20 Feb 2025 01:50:45 +0530 Subject: [PATCH 116/133] fix(connector): [SCRIPT] Update template generating script and updated connector doc (#7301) Co-authored-by: Anurag Singh --- add_connector_updated.md | 720 +++++++++++++++++++++++++++++++++++++++ scripts/add_connector.sh | 26 +- 2 files changed, 742 insertions(+), 4 deletions(-) create mode 100644 add_connector_updated.md diff --git a/add_connector_updated.md b/add_connector_updated.md new file mode 100644 index 00000000000..4ee8c895656 --- /dev/null +++ b/add_connector_updated.md @@ -0,0 +1,720 @@ +# Guide to Integrating a Connector + +## Table of Contents + +1. [Introduction](#introduction) +2. [Prerequisites](#prerequisites) +3. [Understanding Connectors and Payment Methods](#understanding-connectors-and-payment-methods) +4. [Integration Steps](#integration-steps) + - [Generate Template](#generate-template) + - [Implement Request & Response Types](#implement-request--response-types) + - [Implement transformers.rs](#implementing-transformersrs) + - [Handle Response Mapping](#handle-response-mapping) + - [Recommended Fields for Connector Request and Response](#recommended-fields-for-connector-request-and-response) + - [Error Handling](#error-handling) +5. [Implementing the Traits](#implementing-the-traits) + - [ConnectorCommon](#connectorcommon) + - [ConnectorIntegration](#connectorintegration) + - [ConnectorCommonExt](#connectorcommonext) + - [Other Traits](#othertraits) +6. [Set the Currency Unit](#set-the-currency-unit) +7. [Connector utility functions](#connector-utility-functions) +8. [Connector configs for control center](#connector-configs-for-control-center) +9. [Update `ConnectorTypes.res` and `ConnectorUtils.res`](#update-connectortypesres-and-connectorutilsres) +10. [Add Connector Icon](#add-connector-icon) +11. [Test the Connector](#test-the-connector) +12. [Build Payment Request and Response from JSON Schema](#build-payment-request-and-response-from-json-schema) + +## Introduction + +This guide provides instructions on integrating a new connector with Router, from setting up the environment to implementing API interactions. + +## Prerequisites + +- Familiarity with the Connector API you’re integrating +- A locally set up and running Router repository +- API credentials for testing (sign up for sandbox/UAT credentials on the connector’s website). +- Rust nightly toolchain installed for code formatting: + ```bash + rustup toolchain install nightly + ``` + +## Understanding Connectors and Payment Methods + +A **Connector** processes payments (e.g., Stripe, Adyen) or manages fraud risk (e.g., Signifyd). A **Payment Method** is a specific way to transact (e.g., credit card, PayPal). See the [Hyperswitch Payment Matrix](https://hyperswitch.io/pm-list) for details. + +## Integration Steps + +Integrating a connector is mainly an API integration task. You'll define request and response types and implement required traits. + +This tutorial covers card payments via [Billwerk](https://optimize-docs.billwerk.com/). Review the API reference and test APIs before starting. + +Follow these steps to integrate a new connector. + +### Generate Template + +Run the following script to create the connector structure: + +```bash +sh scripts/add_connector.sh +``` + +Example folder structure: + +``` +hyperswitch_connectors/src/connectors +├── billwerk +│ └── transformers.rs +└── billwerk.rs +crates/router/tests/connectors +└── billwerk.rs +``` + +**Note:** move the file `crates/hyperswitch_connectors/src/connectors/connector_name/test.rs` to `crates/router/tests/connectors/connector_name.rs` + + +Define API request/response types and conversions in `hyperswitch_connectors/src/connector/billwerk/transformers.rs` + +Implement connector traits in `hyperswitch_connectors/src/connector/billwerk.rs` + +Write basic payment flow tests in `crates/router/tests/connectors/billwerk.rs` + +Boilerplate code with todo!() is provided—follow the guide and complete the necessary implementations. + +### Implement Request & Response Types + +Integrating a new connector involves transforming Router's core data into the connector's API format. Since the Connector module is stateless, Router handles data persistence. + +#### Implementing transformers.rs + +Design request/response structures based on the connector's API spec. + +Define request format in `transformers.rs`: + +```rust +#[derive(Debug, Serialize)] +pub struct BillwerkCustomerObject { + handle: Option, + email: Option, + address: Option>, + address2: Option>, + city: Option, + country: Option, + first_name: Option>, + last_name: Option>, +} + +#[derive(Debug, Serialize)] +pub struct BillwerkPaymentsRequest { + handle: String, + amount: MinorUnit, + source: Secret, + currency: common_enums::Currency, + customer: BillwerkCustomerObject, + metadata: Option, + settle: bool, +} +``` + +Since Router is connector agnostic, only minimal data is sent to connector and optional fields may be ignored. + +We transform our `PaymentsAuthorizeRouterData` into Billwerk's `PaymentsRequest` structure by employing the `try_from` function. + +```rust +impl TryFrom<&BillwerkRouterData<&types::PaymentsAuthorizeRouterData>> for BillwerkPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &BillwerkRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + if item.router_data.is_three_ds() { + return Err(errors::ConnectorError::NotImplemented( + "Three_ds payments through Billwerk".to_string(), + ) + .into()); + }; + let source = match item.router_data.get_payment_method_token()? { + PaymentMethodToken::Token(pm_token) => Ok(pm_token), + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "payment_method_token", + }), + }?; + Ok(Self { + handle: item.router_data.connector_request_reference_id.clone(), + amount: item.amount, + source, + currency: item.router_data.request.currency, + customer: BillwerkCustomerObject { + handle: item.router_data.customer_id.clone(), + email: item.router_data.request.email.clone(), + address: item.router_data.get_optional_billing_line1(), + address2: item.router_data.get_optional_billing_line2(), + city: item.router_data.get_optional_billing_city(), + country: item.router_data.get_optional_billing_country(), + first_name: item.router_data.get_optional_billing_first_name(), + last_name: item.router_data.get_optional_billing_last_name(), + }, + metadata: item.router_data.request.metadata.clone().map(Into::into), + settle: item.router_data.request.is_auto_capture()?, + }) + } +} +``` + +### Handle Response Mapping + +When implementing the response type, the key enum to define for each connector is `PaymentStatus`. It represents the different status types returned by the connector, as specified in its API spec. Below is the definition for Billwerk. + +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum BillwerkPaymentState { + Created, + Authorized, + Pending, + Settled, + Failed, + Cancelled, +} + +impl From for enums::AttemptStatus { + fn from(item: BillwerkPaymentState) -> Self { + match item { + BillwerkPaymentState::Created | BillwerkPaymentState::Pending => Self::Pending, + BillwerkPaymentState::Authorized => Self::Authorized, + BillwerkPaymentState::Settled => Self::Charged, + BillwerkPaymentState::Failed => Self::Failure, + BillwerkPaymentState::Cancelled => Self::Voided, + } + } +} +``` + +Here are common payment attempt statuses: + +- **Charged:** Payment succeeded. +- **Pending:** Payment is processing. +- **Failure:** Payment failed. +- **Authorized:** Payment authorized; can be voided, captured, or partially captured. +- **AuthenticationPending:** Customer action required. +- **Voided:** Payment voided, funds returned to the customer. + +**Note:** Default status should be `Pending`. Only explicit success or failure from the connector should mark the payment as `Charged` or `Failure`. + +Define response format in `transformers.rs`: + +```rust +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct BillwerkPaymentsResponse { + state: BillwerkPaymentState, + handle: String, + error: Option, + error_state: Option, +} +``` + +We transform our `ResponseRouterData` into `PaymentsResponseData` by employing the `try_from` function. + +```rust +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + let error_response = if item.response.error.is_some() || item.response.error_state.is_some() + { + Some(ErrorResponse { + code: item + .response + .error_state + .clone() + .unwrap_or(NO_ERROR_CODE.to_string()), + message: item + .response + .error_state + .unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: item.response.error, + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: Some(item.response.handle.clone()), + }) + } else { + None + }; + let payments_response = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.handle.clone()), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some(item.response.handle), + incremental_authorization_allowed: None, + charges: None, + }; + Ok(Self { + status: enums::AttemptStatus::from(item.response.state), + response: error_response.map_or_else(|| Ok(payments_response), Err), + ..item.data + }) + } +} +``` + +### Recommended Fields for Connector Request and Response + +- **connector_request_reference_id:** Merchant's reference ID in the payment request (e.g., `reference` in Checkout). + +```rust + reference: item.router_data.connector_request_reference_id.clone(), +``` +- **connector_response_reference_id:** ID used for transaction identification in the connector dashboard, linked to merchant_reference or connector_transaction_id. + +```rust + connector_response_reference_id: item.response.reference.or(Some(item.response.id)), +``` + +- **resource_id:** The connector's connector_transaction_id is used as the resource_id. If unavailable, set to NoResponseId. + +```rust + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id.clone()), +``` + +- **redirection_data:** For redirection flows (e.g., 3D Secure), assign the redirection link. + +```rust + let redirection_data = item.response.links.redirect.map(|href| { + services::RedirectForm::from((href.redirection_url, services::Method::Get)) + }); +``` + +### Error Handling + +Define error responses: + +```rust +#[derive(Debug, Serialize, Deserialize)] +pub struct BillwerkErrorResponse { + pub code: Option, + pub error: String, + pub message: Option, +} +``` + +By following these steps, you can integrate a new connector efficiently while ensuring compatibility with Router's architecture. + +## Implementing the Traits + +The `mod.rs` file contains trait implementations using connector types in transformers. A struct with the connector name holds these implementations. Below are the mandatory traits: + +### ConnectorCommon +Contains common description of the connector, like the base endpoint, content-type, error response handling, id, currency unit. + +Within the `ConnectorCommon` trait, you'll find the following methods : + +- `id` method corresponds directly to the connector name. + +```rust + fn id(&self) -> &'static str { + "Billwerk" + } +``` + +- `get_currency_unit` method anticipates you to [specify the accepted currency unit](#set-the-currency-unit) for the connector. + +```rust + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } +``` + +- `common_get_content_type` method requires you to provide the accepted content type for the connector API. + +```rust + fn common_get_content_type(&self) -> &'static str { + "application/json" + } +``` + +- `get_auth_header` method accepts common HTTP Authorization headers that are accepted in all `ConnectorIntegration` flows. + +```rust + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = BillwerkAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + Ok(vec![( + headers::AUTHORIZATION.to_string(), + format!("Basic {encoded_api_key}").into_masked(), + )]) + } +``` + +- `base_url` method is for fetching the base URL of connector's API. Base url needs to be consumed from configs. + +```rust + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.billwerk.base_url.as_ref() + } +``` + +- `build_error_response` method is common error response handling for a connector if it is same in all cases + +```rust + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: BillwerkErrorResponse = res + .response + .parse_struct("BillwerkErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response + .code + .map_or(NO_ERROR_CODE.to_string(), |code| code.to_string()), + message: response.message.unwrap_or(NO_ERROR_MESSAGE.to_string()), + reason: Some(response.error), + attempt_status: None, + connector_transaction_id: None, + }) + } +``` + +### ConnectorIntegration +For every api endpoint contains the url, using request transform and response transform and headers. +Within the `ConnectorIntegration` trait, you'll find the following methods implemented(below mentioned is example for authorized flow): + +- `get_url` method defines endpoint for authorize flow, base url is consumed from `ConnectorCommon` trait. + +```rust + fn get_url( + &self, + _req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult { + let base_url = connectors + .billwerk + .secondary_base_url + .as_ref() + .ok_or(errors::ConnectorError::FailedToObtainIntegrationUrl)?; + Ok(format!("{base_url}v1/token")) + } +``` + +- `get_headers` method accepts HTTP headers that are accepted for authorize flow. In this context, it is utilized from the `ConnectorCommonExt` trait, as the connector adheres to common headers across various flows. + +```rust + fn get_headers( + &self, + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } +``` + +- `get_request_body` method uses transformers to convert the Hyperswitch payment request to the connector's format. If successful, it returns the request as `RequestContent::Json`, supporting formats like JSON, form-urlencoded, XML, and raw bytes. + +```rust + fn get_request_body( + &self, + req: &TokenizationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = BillwerkTokenRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } +``` + +- `build_request` method assembles the API request by providing the method, URL, headers, and request body as parameters. + +```rust + fn build_request( + &self, + req: &TokenizationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::TokenizationType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::TokenizationType::get_headers(self, req, connectors)?) + .set_body(types::TokenizationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } +``` + +- `handle_response` method calls transformers where connector response data is transformed into hyperswitch response. + +```rust + fn handle_response( + &self, + data: &TokenizationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult + where + PaymentsResponseData: Clone, + { + let response: BillwerkTokenResponse = res + .response + .parse_struct("BillwerkTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } +``` + +- `get_error_response` method to manage error responses. As the handling of checkout errors remains consistent across various flows, we've incorporated it from the `build_error_response` method within the `ConnectorCommon` trait. + +```rust + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +``` + +### ConnectorCommonExt +Adds functions with a generic type, including the `build_headers` method. This method constructs both common headers and Authorization headers (from `get_auth_header`), returning them as a vector. + +```rust + impl ConnectorCommonExt for Billwerk + where + Self: ConnectorIntegration, + { + fn build_headers( + &self, + req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) + } + } +``` + +### OtherTraits +**Payment :** This trait includes several other traits and is meant to represent the functionality related to payments. + +**PaymentAuthorize :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment authorization. + +**PaymentCapture :** This trait extends the `api::ConnectorIntegration `trait with specific types related to manual payment capture. + +**PaymentSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to payment retrieve. + +**Refund :** This trait includes several other traits and is meant to represent the functionality related to Refunds. + +**RefundExecute :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds create. + +**RefundSync :** This trait extends the `api::ConnectorIntegration `trait with specific types related to refunds retrieve. + +And the below derive traits + +- **Debug** +- **Clone** +- **Copy** + +### **Set the currency Unit** + +Part of the `ConnectorCommon` trait, it allows connectors to specify their accepted currency unit as either `Base` or `Minor`. For example, PayPal uses the base unit (e.g., USD), while Hyperswitch uses the minor unit (e.g., cents). Conversion is required if the connector uses the base unit. + +```rust +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for PaypalRouterData +{ + type Error = error_stack::Report; + fn try_from( + (currency_unit, currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_string(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} +``` + +### **Connector utility functions** + +Contains utility functions for constructing connector requests and responses. Use these helpers for retrieving fields like `get_billing_country`, `get_browser_info`, and `get_expiry_date_as_yyyymm`, as well as for validations like `is_three_ds` and `is_auto_capture`. + +```rust + let json_wallet_data: CheckoutGooglePayData = wallet_data.get_wallet_token_as_json()?; +``` + +### **Connector configs for control center** + +This section is for developers using the [Hyperswitch Control Center](https://github.com/juspay/hyperswitch-control-center). Update the connector configuration in development.toml and run the wasm-pack build command. Replace placeholders with actual paths. + +1. Install wasm-pack: + +```bash +cargo install wasm-pack +``` + +2. Add connector configuration: + + Open the development.toml file located at crates/connector_configs/toml/development.toml in your Hyperswitch project. + Find the [stripe] section and add the configuration for example_connector. Example: + + ```toml + # crates/connector_configs/toml/development.toml + + # Other connector configurations... + + [stripe] + [stripe.connector_auth.HeaderKey] + api_key="Secret Key" + + # Add any other Stripe-specific configuration here + + [example_connector] + # Your specific connector configuration for reference + # ... + + ``` + +3. Update paths: + Replace /absolute/path/to/hyperswitch-control-center and /absolute/path/to/hyperswitch with actual paths. + +4. Run `wasm-pack` build: + wasm-pack build --target web --out-dir /absolute/path/to/hyperswitch-control-center/public/hyperswitch/wasm --out-name euclid /absolute/path/to/hyperswitch/crates/euclid_wasm -- --features dummy_connector + +By following these steps, you should be able to update the connector configuration and build the WebAssembly files successfully. + +### Update `ConnectorTypes.res` and `ConnectorUtils.res` + +1. **Update `ConnectorTypes.res`**: + - Open `src/screens/HyperSwitch/Connectors/ConnectorTypes.res`. + - Add your connector to the `connectorName` enum: + ```reason + type connectorName = + | Stripe + | DummyConnector + | YourNewConnector + ``` + - Save the file. + +2. **Update `ConnectorUtils.res`**: + - Open `src/screens/HyperSwitch/Connectors/ConnectorUtils.res`. + - Update functions with your connector: + ```reason + let connectorList : array = [Stripe, YourNewConnector] + + let getConnectorNameString = (connectorName: connectorName) => + switch connectorName { + | Stripe => "Stripe" + | YourNewConnector => "Your New Connector" + }; + + let getConnectorInfo = (connectorName: connectorName) => + switch connectorName { + | Stripe => "Stripe description." + | YourNewConnector => "Your New Connector description." + }; + ``` + - Save the file. + +### Add Connector Icon + +1. **Prepare the Icon**: + Name your connector icon in uppercase (e.g., `YOURCONNECTOR.SVG`). + +2. **Add the Icon**: + Navigate to `public/hyperswitch/Gateway` and copy your SVG icon file there. + +3. **Verify Structure**: + Ensure the file is correctly placed in `public/hyperswitch/Gateway`: + + ``` + public + └── hyperswitch + └── Gateway + └── YOURCONNECTOR.SVG + ``` + Save the changes made to the `Gateway` folder. + +### **Test the Connector** + +1. **Template Code** + + The template script generates a test file with 20 sanity tests. Implement these tests when adding a new connector. + + Example test: + ```rust + #[serial_test::serial] + #[actix_web::test] + async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); + } + ``` + +2. **Utility Functions** + + Helper functions for tests are available in `tests/connector/utils`, making test writing easier. + +3. **Set API Keys** + + Before running tests, configure API keys in sample_auth.toml and set the environment variable: + + ```bash + export CONNECTOR_AUTH_FILE_PATH="/hyperswitch/crates/router/tests/connectors/sample_auth.toml" + cargo test --package router --test connectors -- checkout --test-threads=1 + ``` + +### **Build Payment Request and Response from JSON Schema** + +1. **Install OpenAPI Generator:** + ```bash + brew install openapi-generator + ``` + +2. **Generate Rust Code:** + ```bash + export CONNECTOR_NAME="" + export SCHEMA_PATH="" + openapi-generator generate -g rust -i ${SCHEMA_PATH} -o temp && cat temp/src/models/* > crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && rm -rf temp && sed -i'' -r "s/^pub use.*//;s/^pub mod.*//;s/^\/.*//;s/^.\*.*//;s/crate::models:://g;" crates/router/src/connector/${CONNECTOR_NAME}/temp.rs && cargo +nightly fmt + ``` \ No newline at end of file diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index a2bf5872c07..021854ab3f1 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -52,12 +52,12 @@ previous_connector='' find_prev_connector $payment_gateway previous_connector previous_connector_camelcase="$(tr '[:lower:]' '[:upper:]' <<< ${previous_connector:0:1})${previous_connector:1}" sed -i'' -e "s|pub mod $previous_connector;|pub mod $previous_connector;\npub mod ${payment_gateway};|" $conn.rs -sed -i'' -e "s/};/, ${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs -sed -i'' -e "0,/};/s/};/, ${payment_gateway}, ${payment_gateway}::${payment_gateway_camelcase},\n};/" $src/connector.rs +sed -i'' -e "s/};/ ${payment_gateway}::${payment_gateway_camelcase},\n};/" $conn.rs +sed -i'' -e "/pub use hyperswitch_connectors::connectors::{/ s/{/{\n ${payment_gateway}, ${payment_gateway}::${payment_gateway_camelcase},/" $src/connector.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tenums::Connector::${payment_gateway_camelcase} => Ok(ConnectorEnum::Old(\Box::new(\connector::${payment_gateway_camelcase}))),|" $src/types/api.rs sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tRoutableConnectors::${payment_gateway_camelcase} => euclid_enums::Connector::${payment_gateway_camelcase},|" crates/api_models/src/routing.rs sed -i'' -e "s/pub $previous_connector: \(.*\)/pub $previous_connector: \1\n\tpub ${payment_gateway}: ConnectorParams,/" crates/hyperswitch_interfaces/src/configs.rs -sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml +sed -i'' -e "s|$previous_connector.base_url \(.*\)|$previous_connector.base_url \1\n${payment_gateway}.base_url = \"$base_url\"|" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml config/deployments/integration_test.toml config/deployments/production.toml config/deployments/sandbox.toml sed -r -i'' -e "s/\"$previous_connector\",/\"$previous_connector\",\n \"${payment_gateway}\",/" config/development.toml config/docker_compose.toml config/config.example.toml loadtest/config/development.toml sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/api_models/src/connector_enums.rs sed -i '' -e "/\/\/ Add Separate authentication support for connectors/{N;s/\(.*\)\n/\1\n\t\t\t| Self::${payment_gateway_camelcase}\n/;}" crates/api_models/src/connector_enums.rs @@ -67,9 +67,27 @@ sed -i '' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" c sed -i'' -e "s|$previous_connector_camelcase \(.*\)|$previous_connector_camelcase \1\n\t\t\tapi_enums::Connector::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},|" $src/types/transformers.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations.rs sed -i'' -e "s/^default_imp_for_\(.*\)/default_imp_for_\1\n\tconnectors::${payment_gateway_camelcase},/" crates/hyperswitch_connectors/src/default_implementations_v2.rs +sed -i'' -e "s/^default_imp_for_connector_request_id!(/default_imp_for_connector_request_id!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "s/^default_imp_for_fraud_check!(/default_imp_for_fraud_check!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "s/^default_imp_for_connector_authentication!(/default_imp_for_connector_authentication!(\n connectors::${payment_gateway_camelcase},/" $src/core/payments/flows.rs +sed -i'' -e "/pub struct ConnectorConfig {/ s/{/{\n pub ${payment_gateway}: Option,/" crates/connector_configs/src/connector.rs +sed -i'' -e "/mod utils;/ s/mod utils;/mod ${payment_gateway};\nmod utils;/" crates/router/tests/connectors/main.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_payouts!(/default_imp_for_new_connector_integration_payouts!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_frm!(/default_imp_for_new_connector_integration_frm!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/^default_imp_for_new_connector_integration_connector_authentication!(/default_imp_for_new_connector_integration_connector_authentication!(\n connector::${payment_gateway_camelcase},/" crates/router/src/core/payments/connector_integration_v2_impls.rs +sed -i'' -e "s/\(pub enum Connector {\)/\1\n\t${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match self {/ s/match self {/match self {\n | Self::${payment_gateway_camelcase}/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match routable_connector {/ s/match routable_connector {/match routable_connector {\n RoutableConnectors::${payment_gateway_camelcase} => Self::${payment_gateway_camelcase},/" crates/common_enums/src/connector_enums.rs +sed -i'' -e "/match self.connector_name {/a\\ + api_enums::Connector::${payment_gateway_camelcase} => {\\ + ${payment_gateway}::transformers::${payment_gateway_camelcase}AuthType::try_from(self.auth_type)?;\\ + Ok(())\\ + },\\ +" crates/router/src/core/admin.rs + # Remove temporary files created in above step -rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e +rm $conn.rs-e $src/types/api.rs-e $src/configs/settings.rs-e config/development.toml-e config/docker_compose.toml-e config/config.example.toml-e loadtest/config/development.toml-e crates/api_models/src/connector_enums.rs-e crates/euclid/src/enums.rs-e crates/api_models/src/routing.rs-e $src/core/payments/flows.rs-e crates/common_enums/src/connector_enums.rs-e $src/types/transformers.rs-e $src/core/admin.rs-e crates/hyperswitch_connectors/src/default_implementations.rs-e crates/hyperswitch_connectors/src/default_implementations_v2.rs-e crates/hyperswitch_interfaces/src/configs.rs-e $src/connector.rs-e config/deployments/integration_test.toml-e config/deployments/production.toml-e config/deployments/sandbox.toml-e temp crates/connector_configs/src/connector.rs-e crates/router/tests/connectors/main.rs-e crates/router/src/core/payments/connector_integration_v2_impls.rs-e cd $conn/ # Generate template files for the connector From eea4d6ff8d93cee031670e147343861335884229 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 20 Feb 2025 00:27:29 +0000 Subject: [PATCH 117/133] chore(version): 2025.02.20.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 28565b08385..2d7fbf16041 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.20.0 + +### Features + +- **core:** Add hypersense integration api ([#7218](https://github.com/juspay/hyperswitch/pull/7218)) ([`22633be`](https://github.com/juspay/hyperswitch/commit/22633be55cfc42dc4a7171c3193da594d0557bfb)) + +### Bug Fixes + +- **connector:** [SCRIPT] Update template generating script and updated connector doc ([#7301](https://github.com/juspay/hyperswitch/pull/7301)) ([`2d9df53`](https://github.com/juspay/hyperswitch/commit/2d9df53491b1ef662736efec60ec5e5368466bb4)) + +### Refactors + +- **utils:** Match string for state with SDK's naming convention ([#7300](https://github.com/juspay/hyperswitch/pull/7300)) ([`f3ca200`](https://github.com/juspay/hyperswitch/commit/f3ca2009c1902094a72b8bf43e89b406e44ecfd4)) + +**Full Changelog:** [`2025.02.19.0...2025.02.20.0`](https://github.com/juspay/hyperswitch/compare/2025.02.19.0...2025.02.20.0) + +- - - + ## 2025.02.19.0 ### Features From 74bbf4bf271d45e61e594857707250c95a86f43f Mon Sep 17 00:00:00 2001 From: Sahkal Poddar Date: Thu, 20 Feb 2025 12:47:29 +0530 Subject: [PATCH 118/133] feat(core): add support for confirmation flow for click to pay (#6982) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Co-authored-by: sai-harsha-vardhan Co-authored-by: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> --- .../connectors/cybersource/transformers.rs | 12 +- .../src/connectors/datatrans/transformers.rs | 14 +- .../unified_authentication_service.rs | 110 ++++++- .../transformers.rs | 95 +++++- .../src/connectors/wellsfargo/transformers.rs | 4 +- .../src/default_implementations.rs | 94 +++++- .../unified_authentication_service.rs | 3 + .../src/router_request_types.rs | 4 +- .../unified_authentication_service.rs | 24 +- crates/hyperswitch_domain_models/src/types.rs | 18 +- crates/hyperswitch_interfaces/src/api.rs | 40 ++- crates/hyperswitch_interfaces/src/types.rs | 14 +- .../src/connector/checkout/transformers.rs | 9 +- .../router/src/connector/nmi/transformers.rs | 10 +- crates/router/src/core/payments.rs | 54 +++- .../connector_integration_v2_impls.rs | 14 +- crates/router/src/core/payments/flows.rs | 61 +++- crates/router/src/core/payments/helpers.rs | 11 +- crates/router/src/core/payments/operations.rs | 3 +- .../payments/operations/payment_confirm.rs | 293 ++++++++++-------- crates/router/src/core/payments/types.rs | 17 +- .../core/unified_authentication_service.rs | 94 +++++- .../unified_authentication_service/types.rs | 5 +- .../unified_authentication_service/utils.rs | 187 ++++++----- crates/router/src/types.rs | 3 +- 25 files changed, 906 insertions(+), 287 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index b953fa73754..20db1648e96 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -1270,13 +1270,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); @@ -1353,13 +1353,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); @@ -1434,13 +1434,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index 8a52433275e..e7760c205b6 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -189,12 +189,12 @@ pub enum ThreeDSecureData { #[serde(rename_all = "camelCase")] pub struct ThreeDSData { #[serde(rename = "threeDSTransactionId")] - pub three_ds_transaction_id: Secret, + pub three_ds_transaction_id: Option>, pub cavv: Secret, pub eci: Option, pub xid: Option>, #[serde(rename = "threeDSVersion")] - pub three_ds_version: String, + pub three_ds_version: Option, #[serde(rename = "authenticationResponse")] pub authentication_response: String, } @@ -452,11 +452,17 @@ fn create_card_details( if let Some(auth_data) = &item.router_data.request.authentication_data { details.three_ds = Some(ThreeDSecureData::Authentication(ThreeDSData { - three_ds_transaction_id: Secret::new(auth_data.threeds_server_transaction_id.clone()), + three_ds_transaction_id: auth_data + .threeds_server_transaction_id + .clone() + .map(Secret::new), cavv: Secret::new(auth_data.cavv.clone()), eci: auth_data.eci.clone(), xid: auth_data.ds_trans_id.clone().map(Secret::new), - three_ds_version: auth_data.message_version.to_string(), + three_ds_version: auth_data + .message_version + .clone() + .map(|version| version.to_string()), authentication_response: "Y".to_string(), })); } else if item.router_data.is_three_ds() { diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs index 55f3528bbc4..ac634d58aea 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service.rs @@ -13,19 +13,23 @@ use hyperswitch_domain_models::{ access_token_auth::AccessTokenAuth, payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, refunds::{Execute, RSync}, - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, RefundsData, SetupMandateRequestData, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, + types::{ + UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData, + UasPreAuthenticationRouterData, + }, }; use hyperswitch_interfaces::{ api::{ @@ -71,6 +75,7 @@ impl api::PaymentToken for UnifiedAuthenticationService {} impl api::UnifiedAuthenticationService for UnifiedAuthenticationService {} impl api::UasPreAuthentication for UnifiedAuthenticationService {} impl api::UasPostAuthentication for UnifiedAuthenticationService {} +impl api::UasAuthenticationConfirmation for UnifiedAuthenticationService {} impl api::UasAuthentication for UnifiedAuthenticationService {} impl ConnectorIntegration @@ -168,6 +173,105 @@ impl ConnectorIntegration for UnifiedAuthenticationService +{ + fn get_headers( + &self, + req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}confirmation", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &UasAuthenticationConfirmationRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.transaction_amount, + req.request.transaction_currency, + )?; + + let connector_router_data = + unified_authentication_service::UnifiedAuthenticationServiceRouterData::from(( + amount, req, + )); + let connector_req = + unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationRequest::try_from( + &connector_router_data, + )?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &UasAuthenticationConfirmationRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::UasAuthenticationConfirmationType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::UasAuthenticationConfirmationType::get_headers( + self, req, connectors, + )?) + .set_body(types::UasAuthenticationConfirmationType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &UasAuthenticationConfirmationRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: unified_authentication_service::UnifiedAuthenticationServiceAuthenticateConfirmationResponse = + res.response + .parse_struct("UnifiedAuthenticationService UnifiedAuthenticationServiceAuthenticateConfirmationResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + impl ConnectorIntegration< PreAuthenticate, diff --git a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs index f2ca77d746f..c5d0968fd57 100644 --- a/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/unified_authentication_service/transformers.rs @@ -6,7 +6,10 @@ use hyperswitch_domain_models::{ DynamicData, PostAuthenticationDetails, PreAuthenticationDetails, TokenDetails, UasAuthenticationResponseData, }, - types::{UasPostAuthenticationRouterData, UasPreAuthenticationRouterData}, + types::{ + UasAuthenticationConfirmationRouterData, UasPostAuthenticationRouterData, + UasPreAuthenticationRouterData, + }, }; use hyperswitch_interfaces::errors; use masking::Secret; @@ -46,6 +49,56 @@ pub struct UnifiedAuthenticationServicePreAuthenticateRequest { pub transaction_details: Option, } +#[derive(Debug, Serialize, PartialEq)] +pub struct UnifiedAuthenticationServiceAuthenticateConfirmationRequest { + pub authenticate_by: String, + pub source_authentication_id: String, + pub auth_creds: AuthType, + pub x_src_flow_id: Option, + pub transaction_amount: Option, + pub transaction_currency: Option, + pub checkout_event_type: Option, + pub checkout_event_status: Option, + pub confirmation_status: Option, + pub confirmation_reason: Option, + pub confirmation_timestamp: Option, + pub network_authorization_code: Option, + pub network_transaction_identifier: Option, + pub correlation_id: Option, + pub merchant_transaction_id: Option, +} + +#[derive(Debug, Serialize, PartialEq, Deserialize)] +pub struct UnifiedAuthenticationServiceAuthenticateConfirmationResponse { + status: String, +} + +impl + TryFrom< + ResponseRouterData< + F, + UnifiedAuthenticationServiceAuthenticateConfirmationResponse, + T, + UasAuthenticationResponseData, + >, + > for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData< + F, + UnifiedAuthenticationServiceAuthenticateConfirmationResponse, + T, + UasAuthenticationResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(UasAuthenticationResponseData::Confirmation {}), + ..item.data + }) + } +} + #[derive(Debug, Serialize, PartialEq)] #[serde(tag = "auth_type")] pub enum AuthType { @@ -443,3 +496,43 @@ impl pub struct UnifiedAuthenticationServiceErrorResponse { pub error: String, } + +impl TryFrom<&UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>> + for UnifiedAuthenticationServiceAuthenticateConfirmationRequest +{ + type Error = error_stack::Report; + fn try_from( + item: &UnifiedAuthenticationServiceRouterData<&UasAuthenticationConfirmationRouterData>, + ) -> Result { + let auth_type = + UnifiedAuthenticationServiceAuthType::try_from(&item.router_data.connector_auth_type)?; + let authentication_id = item.router_data.authentication_id.clone().ok_or( + errors::ConnectorError::MissingRequiredField { + field_name: "authentication_id", + }, + )?; + Ok(Self { + authenticate_by: item.router_data.connector.clone(), + auth_creds: AuthType::HeaderKey { + api_key: auth_type.api_key, + }, + source_authentication_id: authentication_id, + x_src_flow_id: item.router_data.request.x_src_flow_id.clone(), + transaction_amount: Some(item.amount), + transaction_currency: Some(item.router_data.request.transaction_currency), + checkout_event_type: item.router_data.request.checkout_event_type.clone(), + checkout_event_status: item.router_data.request.checkout_event_status.clone(), + confirmation_status: item.router_data.request.confirmation_status.clone(), + confirmation_reason: item.router_data.request.confirmation_reason.clone(), + confirmation_timestamp: item.router_data.request.confirmation_timestamp, + network_authorization_code: item.router_data.request.network_authorization_code.clone(), + network_transaction_identifier: item + .router_data + .request + .network_transaction_identifier + .clone(), + correlation_id: item.router_data.request.correlation_id.clone(), + merchant_transaction_id: item.router_data.request.merchant_transaction_id.clone(), + }) + } +} diff --git a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs index f046b8de707..10c381fa857 100644 --- a/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/wellsfargo/transformers.rs @@ -941,13 +941,13 @@ impl ucaf_collection_indicator: None, cavv, ucaf_authentication_data, - xid: Some(authn_data.threeds_server_transaction_id.clone()), + xid: authn_data.threeds_server_transaction_id.clone(), directory_server_transaction_id: authn_data .ds_trans_id .clone() .map(Secret::new), specification_version: None, - pa_specification_version: Some(authn_data.message_version.clone()), + pa_specification_version: authn_data.message_version.clone(), veres_enrolled: Some("Y".to_string()), } }); diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index a88de8331c3..cd78cb47f9d 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -33,12 +33,13 @@ use hyperswitch_domain_models::{ PreProcessing, Reject, SdkSessionUpdate, }, webhooks::VerifyWebhookSource, - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, MandateRevokeRequestData, @@ -75,7 +76,8 @@ use hyperswitch_interfaces::{ PaymentsPreProcessing, TaxCalculation, }, ConnectorIntegration, ConnectorMandateRevoke, ConnectorRedirectResponse, UasAuthentication, - UasPostAuthentication, UasPreAuthentication, UnifiedAuthenticationService, + UasAuthenticationConfirmation, UasPostAuthentication, UasPreAuthentication, + UnifiedAuthenticationService, }, errors::ConnectorError, }; @@ -3099,3 +3101,89 @@ default_imp_for_uas_authentication!( connectors::Zen, connectors::Zsl ); + +macro_rules! default_imp_for_uas_authentication_confirmation { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthenticationConfirmation for $path::$connector {} + impl + ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData + + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_authentication_confirmation!( + connectors::Aci, + connectors::Airwallex, + connectors::Amazonpay, + connectors::Bambora, + connectors::Bamboraapac, + connectors::Bankofamerica, + connectors::Billwerk, + connectors::Bluesnap, + connectors::Bitpay, + connectors::Braintree, + connectors::Boku, + connectors::Cashtocode, + connectors::Chargebee, + connectors::Coinbase, + connectors::Coingate, + connectors::Cryptopay, + connectors::CtpMastercard, + connectors::Cybersource, + connectors::Datatrans, + connectors::Deutschebank, + connectors::Digitalvirgo, + connectors::Dlocal, + connectors::Elavon, + connectors::Fiserv, + connectors::Fiservemea, + connectors::Fiuu, + connectors::Forte, + connectors::Getnet, + connectors::Globalpay, + connectors::Globepay, + connectors::Gocardless, + connectors::Helcim, + connectors::Iatapay, + connectors::Inespay, + connectors::Itaubank, + connectors::Jpmorgan, + connectors::Klarna, + connectors::Nomupay, + connectors::Novalnet, + connectors::Nexinets, + connectors::Nexixpay, + connectors::Nuvei, + connectors::Payeezy, + connectors::Payu, + connectors::Powertranz, + connectors::Prophetpay, + connectors::Mifinity, + connectors::Mollie, + connectors::Moneris, + connectors::Multisafepay, + connectors::Paybox, + connectors::Placetopay, + connectors::Rapyd, + connectors::Razorpay, + connectors::Redsys, + connectors::Shift4, + connectors::Stax, + connectors::Square, + connectors::Taxjar, + connectors::Thunes, + connectors::Tsys, + connectors::Worldline, + connectors::Worldpay, + connectors::Wellsfargo, + connectors::Volt, + connectors::Xendit, + connectors::Zen, + connectors::Zsl +); diff --git a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs index ddc56eb2a29..ba851e05060 100644 --- a/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_flow_types/unified_authentication_service.rs @@ -4,5 +4,8 @@ pub struct PreAuthenticate; #[derive(Debug, Clone)] pub struct PostAuthenticate; +#[derive(Debug, Clone)] +pub struct AuthenticationConfirmation; + #[derive(Debug, Clone)] pub struct Authenticate; diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 13ab97d60dc..90f97816091 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -604,8 +604,8 @@ impl pub struct AuthenticationData { pub eci: Option, pub cavv: String, - pub threeds_server_transaction_id: String, - pub message_version: common_utils::types::SemanticVersion, + pub threeds_server_transaction_id: Option, + pub message_version: Option, pub ds_trans_id: Option, } diff --git a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs index 4238c513f44..248fec6f05e 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types/unified_authentication_service.rs @@ -1,5 +1,7 @@ use api_models::payments::DeviceChannel; +use common_utils::types::MinorUnit; use masking::Secret; +use time::PrimitiveDateTime; use crate::{address::Address, payment_method_data::PaymentMethodData}; @@ -53,7 +55,7 @@ pub struct ServiceSessionIds { #[derive(Debug, serde::Serialize, serde::Deserialize, Clone)] pub struct TransactionDetails { - pub amount: Option, + pub amount: Option, pub currency: Option, pub device_channel: Option, pub message_category: Option, @@ -75,6 +77,7 @@ pub enum UasAuthenticationResponseData { PostAuthentication { authentication_details: PostAuthenticationDetails, }, + Confirmation {}, } #[derive(Debug, Clone)] @@ -120,3 +123,22 @@ pub struct DynamicData { pub dynamic_data_type: Option, pub ds_trans_id: Option, } + +#[derive(serde::Serialize, serde::Deserialize, Debug, Clone)] +pub struct UasConfirmationRequestData { + pub x_src_flow_id: Option, + pub transaction_amount: MinorUnit, + pub transaction_currency: common_enums::Currency, + // Type of event associated with the checkout. Valid values are: - 01 - Authorise - 02 - Capture - 03 - Refund - 04 - Cancel - 05 - Fraud - 06 - Chargeback - 07 - Other + pub checkout_event_type: Option, + pub checkout_event_status: Option, + pub confirmation_status: Option, + pub confirmation_reason: Option, + pub confirmation_timestamp: Option, + // Authorisation code associated with an approved transaction. + pub network_authorization_code: Option, + // The unique authorisation related tracing value assigned by a Payment Network and provided in an authorisation response. Required only when checkoutEventType=01. If checkoutEventType=01 and the value of networkTransactionIdentifier is unknown, please pass UNAVLB + pub network_transaction_identifier: Option, + pub correlation_id: Option, + pub merchant_transaction_id: Option, +} diff --git a/crates/hyperswitch_domain_models/src/types.rs b/crates/hyperswitch_domain_models/src/types.rs index dc64560ead6..a774dda3c95 100644 --- a/crates/hyperswitch_domain_models/src/types.rs +++ b/crates/hyperswitch_domain_models/src/types.rs @@ -3,15 +3,17 @@ pub use diesel_models::types::OrderDetailsWithAmount; use crate::{ router_data::{AccessToken, RouterData}, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, Authorize, - AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, CreateConnectorCustomer, - Execute, IncrementalAuthorization, PSync, PaymentMethodToken, PostAuthenticate, - PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, SetupMandate, Void, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, AuthenticationConfirmation, + Authorize, AuthorizeSessionToken, CalculateTax, Capture, CompleteAuthorize, + CreateConnectorCustomer, Execute, IncrementalAuthorization, PSync, PaymentMethodToken, + PostAuthenticate, PostSessionTokens, PreAuthenticate, PreProcessing, RSync, Session, + SetupMandate, Void, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, MandateRevokeRequestData, PaymentMethodTokenizationData, @@ -60,6 +62,12 @@ pub type UasPostAuthenticationRouterData = pub type UasPreAuthenticationRouterData = RouterData; +pub type UasAuthenticationConfirmationRouterData = RouterData< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +>; + pub type MandateRevokeRouterData = RouterData; pub type PaymentsIncrementalAuthorizationRouterData = RouterData< diff --git a/crates/hyperswitch_interfaces/src/api.rs b/crates/hyperswitch_interfaces/src/api.rs index ab918b5334b..35a3da46330 100644 --- a/crates/hyperswitch_interfaces/src/api.rs +++ b/crates/hyperswitch_interfaces/src/api.rs @@ -34,13 +34,14 @@ use hyperswitch_domain_models::{ UasFlowData, }, router_flow_types::{ - mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, PostAuthenticate, - PreAuthenticate, VerifyWebhookSource, + mandate_revoke::MandateRevoke, AccessTokenAuth, Authenticate, AuthenticationConfirmation, + PostAuthenticate, PreAuthenticate, VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AccessTokenRequestData, MandateRevokeRequestData, VerifyWebhookSourceRequestData, }, @@ -371,7 +372,11 @@ pub trait ConnectorVerifyWebhookSourceV2: /// trait UnifiedAuthenticationService pub trait UnifiedAuthenticationService: - ConnectorCommon + UasPreAuthentication + UasPostAuthentication + UasAuthentication + ConnectorCommon + + UasPreAuthentication + + UasPostAuthentication + + UasAuthenticationConfirmation + + UasAuthentication { } @@ -395,6 +400,16 @@ pub trait UasPostAuthentication: { } +/// trait UasAuthenticationConfirmation +pub trait UasAuthenticationConfirmation: + ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait UasAuthentication pub trait UasAuthentication: ConnectorIntegration @@ -403,7 +418,11 @@ pub trait UasAuthentication: /// trait UnifiedAuthenticationServiceV2 pub trait UnifiedAuthenticationServiceV2: - ConnectorCommon + UasPreAuthenticationV2 + UasPostAuthenticationV2 + UasAuthenticationV2 + ConnectorCommon + + UasPreAuthenticationV2 + + UasPostAuthenticationV2 + + UasAuthenticationV2 + + UasAuthenticationConfirmationV2 { } @@ -429,6 +448,17 @@ pub trait UasPostAuthenticationV2: { } +/// trait UasAuthenticationConfirmationV2 +pub trait UasAuthenticationConfirmationV2: + ConnectorIntegrationV2< + AuthenticationConfirmation, + UasFlowData, + UasConfirmationRequestData, + UasAuthenticationResponseData, +> +{ +} + /// trait UasAuthenticationV2 pub trait UasAuthenticationV2: ConnectorIntegrationV2< diff --git a/crates/hyperswitch_interfaces/src/types.rs b/crates/hyperswitch_interfaces/src/types.rs index a047e7f00d7..d7c7360a2db 100644 --- a/crates/hyperswitch_interfaces/src/types.rs +++ b/crates/hyperswitch_interfaces/src/types.rs @@ -14,13 +14,16 @@ use hyperswitch_domain_models::{ Session, SetupMandate, Void, }, refunds::{Execute, RSync}, - unified_authentication_service::{Authenticate, PostAuthenticate, PreAuthenticate}, + unified_authentication_service::{ + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, + }, webhooks::VerifyWebhookSource, }, router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, CompleteAuthorizeData, ConnectorCustomerData, DefendDisputeRequestData, @@ -206,6 +209,13 @@ pub type UasPostAuthenticationType = dyn ConnectorIntegration< UasAuthenticationResponseData, >; +/// Type alias for `ConnectorIntegration` +pub type UasAuthenticationConfirmationType = dyn ConnectorIntegration< + AuthenticationConfirmation, + UasConfirmationRequestData, + UasAuthenticationResponseData, +>; + /// Type alias for `ConnectorIntegration` pub type UasAuthenticationType = dyn ConnectorIntegration< Authenticate, diff --git a/crates/router/src/connector/checkout/transformers.rs b/crates/router/src/connector/checkout/transformers.rs index 254c0346e8e..4d9f5978b08 100644 --- a/crates/router/src/connector/checkout/transformers.rs +++ b/crates/router/src/connector/checkout/transformers.rs @@ -407,8 +407,13 @@ impl TryFrom<&CheckoutRouterData<&types::PaymentsAuthorizeRouterData>> for Payme force_3ds: true, eci: authentication_data.and_then(|auth| auth.eci.clone()), cryptogram: authentication_data.map(|auth| auth.cavv.clone()), - xid: authentication_data.map(|auth| auth.threeds_server_transaction_id.clone()), - version: authentication_data.map(|auth| auth.message_version.to_string()), + xid: authentication_data + .and_then(|auth| auth.threeds_server_transaction_id.clone()), + version: authentication_data.and_then(|auth| { + auth.message_version + .clone() + .map(|version| version.to_string()) + }), }, enums::AuthenticationType::NoThreeDs => CheckoutThreeDS { enabled: false, diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index e70813216d2..1e8b745107d 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -620,8 +620,14 @@ impl TryFrom<(&domain::payments::Card, &types::PaymentsAuthorizeData)> for Payme cavv: Some(auth_data.cavv.clone()), eci: auth_data.eci.clone(), cardholder_auth: None, - three_ds_version: Some(auth_data.message_version.to_string()), - directory_server_id: Some(auth_data.threeds_server_transaction_id.clone().into()), + three_ds_version: auth_data + .message_version + .clone() + .map(|version| version.to_string()), + directory_server_id: auth_data + .threeds_server_transaction_id + .clone() + .map(Secret::new), }; Ok(Self::CardThreeDs(Box::new(card_3ds_details))) diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 6edd2b7c65d..a14d8b14df8 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -388,9 +388,12 @@ where should_continue_capture, ); - if helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), state) - .await? - { + let is_eligible_for_uas = + helpers::is_merchant_eligible_authentication_service(merchant_account.get_id(), state) + .await?; + + if is_eligible_for_uas { + let should_do_uas_confirmation_call = false; operation .to_domain()? .call_unified_authentication_service_if_eligible( @@ -401,6 +404,7 @@ where &business_profile, &key_store, mandate_type, + &should_do_uas_confirmation_call, ) .await?; } else { @@ -459,7 +463,7 @@ where _ => (), }; payment_data = match connector_details { - ConnectorCallType::PreDetermined(connector) => { + ConnectorCallType::PreDetermined(ref connector) => { #[cfg(all(feature = "dynamic_routing", feature = "v1"))] let routable_connectors = convert_connector_data_to_routable_connectors(&[connector.clone()]) @@ -500,6 +504,23 @@ where ) .await?; + if is_eligible_for_uas { + let should_do_uas_confirmation_call = true; + operation + .to_domain()? + .call_unified_authentication_service_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + mandate_type, + &should_do_uas_confirmation_call, + ) + .await?; + } + let op_ref = &operation; let should_trigger_post_processing_flows = is_operation_confirm(&operation); @@ -545,7 +566,7 @@ where &key_store, &customer, &mca, - &connector, + connector, &mut payment_data, op_ref, Some(header_payload.clone()), @@ -556,14 +577,14 @@ where payment_data } - ConnectorCallType::Retryable(connectors) => { + ConnectorCallType::Retryable(ref connectors) => { #[cfg(all(feature = "dynamic_routing", feature = "v1"))] let routable_connectors = - convert_connector_data_to_routable_connectors(&connectors) + convert_connector_data_to_routable_connectors(connectors) .map_err(|e| logger::error!(routable_connector_error=?e)) .unwrap_or_default(); - let mut connectors = connectors.into_iter(); + let mut connectors = connectors.clone().into_iter(); let connector_data = get_connector_data(&mut connectors)?; @@ -641,6 +662,23 @@ where let op_ref = &operation; let should_trigger_post_processing_flows = is_operation_confirm(&operation); + if is_eligible_for_uas { + let should_do_uas_confirmation_call = true; + operation + .to_domain()? + .call_unified_authentication_service_if_eligible( + state, + &mut payment_data, + &mut should_continue_transaction, + &connector_details, + &business_profile, + &key_store, + mandate_type, + &should_do_uas_confirmation_call, + ) + .await?; + } + let operation = Box::new(PaymentResponse); connector_http_status_code = router_data.connector_http_status_code; external_latency = router_data.external_latency; diff --git a/crates/router/src/core/payments/connector_integration_v2_impls.rs b/crates/router/src/core/payments/connector_integration_v2_impls.rs index 03006706e25..1219363b212 100644 --- a/crates/router/src/core/payments/connector_integration_v2_impls.rs +++ b/crates/router/src/core/payments/connector_integration_v2_impls.rs @@ -1,9 +1,9 @@ use hyperswitch_domain_models::router_flow_types::{ - Authenticate, PostAuthenticate, PreAuthenticate, + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, }; use hyperswitch_interfaces::api::{ - UasAuthenticationV2, UasPostAuthenticationV2, UasPreAuthenticationV2, - UnifiedAuthenticationServiceV2, + UasAuthenticationConfirmationV2, UasAuthenticationV2, UasPostAuthenticationV2, + UasPreAuthenticationV2, UnifiedAuthenticationServiceV2, }; #[cfg(feature = "frm")] @@ -1907,6 +1907,7 @@ macro_rules! default_imp_for_new_connector_integration_uas { $( impl UnifiedAuthenticationServiceV2 for $path::$connector {} impl UasPreAuthenticationV2 for $path::$connector {} impl UasPostAuthenticationV2 for $path::$connector {} + impl UasAuthenticationConfirmationV2 for $path::$connector {} impl UasAuthenticationV2 for $path::$connector {} impl services::ConnectorIntegrationV2< @@ -1926,6 +1927,13 @@ macro_rules! default_imp_for_new_connector_integration_uas { {} impl services::ConnectorIntegrationV2< + AuthenticationConfirmation, + types::UasFlowData, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData, + > for $path::$connector + {} + impl services::ConnectorIntegrationV2< Authenticate, types::UasFlowData, types::UasAuthenticationRequestData, diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 1aa5ed66294..f55c1f58f60 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -14,12 +14,14 @@ pub mod setup_mandate_flow; use async_trait::async_trait; use hyperswitch_domain_models::{ mandates::CustomerAcceptance, - router_flow_types::{Authenticate, PostAuthenticate, PreAuthenticate}, + router_flow_types::{ + Authenticate, AuthenticationConfirmation, PostAuthenticate, PreAuthenticate, + }, router_request_types::PaymentsCaptureData, }; use hyperswitch_interfaces::api::{ - payouts::Payouts, UasAuthentication, UasPostAuthentication, UasPreAuthentication, - UnifiedAuthenticationService, + payouts::Payouts, UasAuthentication, UasAuthenticationConfirmation, UasPostAuthentication, + UasPreAuthentication, UnifiedAuthenticationService, }; #[cfg(feature = "frm")] @@ -2276,6 +2278,57 @@ default_imp_for_uas_post_authentication!( connector::Wise ); +macro_rules! default_imp_for_uas_authentication_confirmation { + ($($path:ident::$connector:ident),*) => { + $( impl UasAuthenticationConfirmation for $path::$connector {} + impl + services::ConnectorIntegration< + AuthenticationConfirmation, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData + > for $path::$connector + {} + )* + }; +} + +default_imp_for_uas_authentication_confirmation!( + connector::Adyenplatform, + connector::Adyen, + connector::Authorizedotnet, + connector::Checkout, + connector::Ebanx, + connector::Gpayments, + connector::Netcetera, + connector::Nmi, + connector::Noon, + connector::Opayo, + connector::Opennode, + connector::Payme, + connector::Payone, + connector::Paypal, + connector::Plaid, + connector::Riskified, + connector::Signifyd, + connector::Stripe, + connector::Threedsecureio, + connector::Trustpay, + connector::Wellsfargopayout, + connector::Wise +); + +#[cfg(feature = "dummy_connector")] +impl UasAuthenticationConfirmation for connector::DummyConnector {} +#[cfg(feature = "dummy_connector")] +impl + services::ConnectorIntegration< + AuthenticationConfirmation, + types::UasConfirmationRequestData, + types::UasAuthenticationResponseData, + > for connector::DummyConnector +{ +} + macro_rules! default_imp_for_uas_authentication { ($($path:ident::$connector:ident),*) => { $( impl UasAuthentication for $path::$connector {} @@ -2289,6 +2342,7 @@ macro_rules! default_imp_for_uas_authentication { )* }; } + #[cfg(feature = "dummy_connector")] impl UasAuthentication for connector::DummyConnector {} #[cfg(feature = "dummy_connector")] @@ -2325,6 +2379,7 @@ default_imp_for_uas_authentication!( connector::Wellsfargopayout, connector::Wise ); + /// Determines whether a capture API call should be made for a payment attempt /// This function evaluates whether an authorized payment should proceed with a capture API call /// based on various payment parameters. It's primarily used in two-step (auth + capture) payment flows for CaptureMethod SequentialAutomatic diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 6702b94c43b..4bfc0db5537 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6546,6 +6546,7 @@ pub enum UnifiedAuthenticationServiceFlow { ExternalAuthenticationPostAuthenticate { authentication_id: String, }, + ClickToPayConfirmation, } #[cfg(feature = "v1")] @@ -6556,6 +6557,7 @@ pub async fn decide_action_for_unified_authentication_service( payment_data: &mut PaymentData, connector_call_type: &api::ConnectorCallType, mandate_type: Option, + do_authorisation_confirmation: &bool, ) -> RouterResult> { let external_authentication_flow = get_payment_external_authentication_flow_during_confirm( state, @@ -6586,7 +6588,9 @@ pub async fn decide_action_for_unified_authentication_service( ) } None => { - if let Some(payment_method) = payment_data.payment_attempt.payment_method { + if *do_authorisation_confirmation { + Some(UnifiedAuthenticationServiceFlow::ClickToPayConfirmation) + } else if let Some(payment_method) = payment_data.payment_attempt.payment_method { if payment_method == storage_enums::PaymentMethod::Card && business_profile.is_click_to_pay_enabled && payment_data.service_details.is_some() @@ -6596,6 +6600,11 @@ pub async fn decide_action_for_unified_authentication_service( None } } else { + logger::info!( + payment_method=?payment_data.payment_attempt.payment_method, + click_to_pay_enabled=?business_profile.is_click_to_pay_enabled, + "skipping unified authentication service call since payment conditions are not satisfied" + ); None } } diff --git a/crates/router/src/core/payments/operations.rs b/crates/router/src/core/payments/operations.rs index e345abbf9c4..b305370f6c1 100644 --- a/crates/router/src/core/payments/operations.rs +++ b/crates/router/src/core/payments/operations.rs @@ -318,9 +318,10 @@ pub trait Domain: Send + Sync { _payment_data: &mut D, _should_continue_confirm_transaction: &mut bool, _connector_call_type: &ConnectorCallType, - _merchant_account: &domain::Profile, + _business_profile: &domain::Profile, _key_store: &domain::MerchantKeyStore, _mandate_type: Option, + _do_authorization_confirmation: &bool, ) -> CustomResult<(), errors::ApiErrorResponse> { Ok(()) } diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 84ff9933875..6068eba1319 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -1050,6 +1050,7 @@ impl Domain> for business_profile: &domain::Profile, key_store: &domain::MerchantKeyStore, mandate_type: Option, + do_authorisation_confirmation: &bool, ) -> CustomResult<(), errors::ApiErrorResponse> { let unified_authentication_service_flow = helpers::decide_action_for_unified_authentication_service( @@ -1059,135 +1060,180 @@ impl Domain> for payment_data, connector_call_type, mandate_type, + do_authorisation_confirmation, ) .await?; + if let Some(unified_authentication_service_flow) = unified_authentication_service_flow { match unified_authentication_service_flow { - helpers::UnifiedAuthenticationServiceFlow::ClickToPayInitiate => { - let authentication_product_ids = business_profile - .authentication_product_ids - .clone() - .ok_or(errors::ApiErrorResponse::PreconditionFailed { - message: "authentication_product_ids is not configured in business profile" - .to_string(), - })?; - let click_to_pay_mca_id = authentication_product_ids - .get_click_to_pay_connector_account_id() - .change_context(errors::ApiErrorResponse::MissingRequiredField { - field_name: "authentication_product_ids", - })?; - - let key_manager_state = &(state).into(); - let merchant_id = &business_profile.merchant_id; - - let connector_mca = state - .store - .find_by_merchant_connector_account_merchant_id_merchant_connector_id( - key_manager_state, - merchant_id, - &click_to_pay_mca_id, - key_store, - ) - .await - .to_not_found_response( - errors::ApiErrorResponse::MerchantConnectorAccountNotFound { - id: click_to_pay_mca_id.get_string_repr().to_string(), - }, - )?; - - let authentication_id = - common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); - - let payment_method = payment_data.payment_attempt.payment_method.ok_or( - errors::ApiErrorResponse::MissingRequiredField { - field_name: "payment_method", - }, - )?; - - ClickToPay::pre_authentication( - state, - key_store, - business_profile, - payment_data, - &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), - &connector_mca.connector_name, - &authentication_id, - payment_method, - ) - .await?; - - payment_data.payment_attempt.authentication_id = Some(authentication_id.clone()); + helpers::UnifiedAuthenticationServiceFlow::ClickToPayInitiate => { + let authentication_product_ids = business_profile + .authentication_product_ids + .clone() + .ok_or(errors::ApiErrorResponse::PreconditionFailed { + message: "authentication_product_ids is not configured in business profile" + .to_string(), + })?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "authentication_product_ids", + })?; + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; + let authentication_id = + common_utils::generate_id_with_default_len(consts::AUTHENTICATION_ID_PREFIX); + let payment_method = payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method", + }, + )?; + ClickToPay::pre_authentication( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + &authentication_id, + payment_method, + ) + .await?; - let response = ClickToPay::post_authentication( - state, - key_store, - business_profile, - payment_data, - &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), - &connector_mca.connector_name, - payment_method, - None, - ) - .await?; + payment_data.payment_attempt.authentication_id = Some(authentication_id.clone()); + let response = ClickToPay::post_authentication( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + payment_method, + None, + ) + .await?; + let (network_token, authentication_status) = match response.response.clone() { + Ok(unified_authentication_service::UasAuthenticationResponseData::PostAuthentication { + authentication_details, + }) => { + let token_details = authentication_details.token_details.ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Missing authentication_details.token_details")?; + (Some( + hyperswitch_domain_models::payment_method_data::NetworkTokenData { + token_number: token_details.payment_token, + token_exp_month: token_details + .token_expiration_month, + token_exp_year: token_details + .token_expiration_year, + token_cryptogram: authentication_details + .dynamic_data_details + .and_then(|data| data.dynamic_data_value), + card_issuer: None, + card_network: None, + card_type: None, + card_issuing_country: None, + bank_code: None, + nick_name: None, + eci: authentication_details.eci, + }),common_enums::AuthenticationStatus::Success) + }, - let (network_token, authentication_status) = match response.response.clone() { - Ok(unified_authentication_service::UasAuthenticationResponseData::PostAuthentication { - authentication_details, - }) => { - let token_details = authentication_details.token_details.ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Missing authentication_details.token_details")?; - (Some( - hyperswitch_domain_models::payment_method_data::NetworkTokenData { - token_number: token_details.payment_token, - token_exp_month: token_details - .token_expiration_month, - token_exp_year: token_details - .token_expiration_year, - token_cryptogram: authentication_details - .dynamic_data_details - .and_then(|data| data.dynamic_data_value), - card_issuer: None, - card_network: None, - card_type: None, - card_issuing_country: None, - bank_code: None, - nick_name: None, - eci: authentication_details.eci, - }),common_enums::AuthenticationStatus::Success) - }, - Ok(unified_authentication_service::UasAuthenticationResponseData::Authentication { .. }) - | Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication { .. }) - => Err(errors::ApiErrorResponse::InternalServerError).attach_printable("unexpected response received")?, - Err(_) => (None, common_enums::AuthenticationStatus::Failed), - }; + Ok(unified_authentication_service::UasAuthenticationResponseData::PreAuthentication { .. }) + | Ok(unified_authentication_service::UasAuthenticationResponseData::Confirmation {}) + | Ok(unified_authentication_service::UasAuthenticationResponseData::Authentication { .. }) => Err(errors::ApiErrorResponse::InternalServerError).attach_printable("unexpected response received from unified authentication service")?, + Err(_) => (None, common_enums::AuthenticationStatus::Failed) + }; + payment_data.payment_attempt.payment_method = + Some(common_enums::PaymentMethod::Card); - payment_data.payment_attempt.payment_method = - Some(common_enums::PaymentMethod::Card); + payment_data.payment_method_data = network_token + .clone() + .map(domain::PaymentMethodData::NetworkToken); - payment_data.payment_method_data = network_token + let authentication = uas_utils::create_new_authentication( + state, + payment_data.payment_attempt.merchant_id.clone(), + connector_mca.connector_name.to_string(), + business_profile.get_id().clone(), + Some(payment_data.payment_intent.get_id().clone()), + click_to_pay_mca_id.to_owned(), + &authentication_id, + payment_data.service_details.clone(), + authentication_status, + network_token, + payment_data.payment_attempt.organization_id.clone(), + ) + .await?; + payment_data.authentication = Some(authentication); + }, + helpers::UnifiedAuthenticationServiceFlow::ClickToPayConfirmation => { + let authentication_product_ids = business_profile + .authentication_product_ids .clone() - .map(domain::PaymentMethodData::NetworkToken); + .ok_or(errors::ApiErrorResponse::PreconditionFailed { + message: "authentication_product_ids is not configured in business profile" + .to_string(), + })?; + let click_to_pay_mca_id = authentication_product_ids + .get_click_to_pay_connector_account_id() + .change_context(errors::ApiErrorResponse::MissingRequiredField { + field_name: "click_to_pay_mca_id", + })?; + let key_manager_state = &(state).into(); + let merchant_id = &business_profile.merchant_id; + + let connector_mca = state + .store + .find_by_merchant_connector_account_merchant_id_merchant_connector_id( + key_manager_state, + merchant_id, + &click_to_pay_mca_id, + key_store, + ) + .await + .to_not_found_response( + errors::ApiErrorResponse::MerchantConnectorAccountNotFound { + id: click_to_pay_mca_id.get_string_repr().to_string(), + }, + )?; - uas_utils::create_new_authentication( - state, - payment_data.payment_attempt.merchant_id.clone(), - connector_mca.connector_name.to_string(), - business_profile.get_id().clone(), - Some(payment_data.payment_intent.get_id().clone()), - click_to_pay_mca_id.to_owned(), - &authentication_id, - payment_data.service_details.clone(), - authentication_status, - payment_data.payment_attempt.organization_id.clone(), - ) - .await?; - }, - helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { - acquirer_details, - token, - .. - } => { - let (authentication_connector, three_ds_connector_account) = + let payment_method = payment_data.payment_attempt.payment_method.ok_or( + errors::ApiErrorResponse::MissingRequiredField { + field_name: "payment_method", + }, + )?; + + ClickToPay::confirmation( + state, + key_store, + business_profile, + payment_data, + &helpers::MerchantConnectorAccountType::DbVal(Box::new(connector_mca.clone())), + &connector_mca.connector_name, + payment_method, + ) + .await? + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationInitiate { + acquirer_details, + token, + .. + } => { + let (authentication_connector, three_ds_connector_account) = authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; let authentication_connector_name = authentication_connector.to_string(); let authentication = authentication::utils::create_new_authentication( @@ -1252,9 +1298,9 @@ impl Domain> for .attach_printable("Error while parsing PollConfig")?; payment_data.poll_config = Some(poll_config) } - }, - helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {authentication_id} => { - let (authentication_connector, three_ds_connector_account) = + }, + helpers::UnifiedAuthenticationServiceFlow::ExternalAuthenticationPostAuthenticate {authentication_id} => { + let (authentication_connector, three_ds_connector_account) = authentication::utils::get_authentication_connector_data(state, key_store, business_profile).await?; let is_pull_mechanism_enabled = utils::check_if_pull_mechanism_for_external_3ds_enabled_from_connector_metadata( @@ -1301,8 +1347,9 @@ impl Domain> for *should_continue_confirm_transaction = false; } }, + } } - } + Ok(()) } diff --git a/crates/router/src/core/payments/types.rs b/crates/router/src/core/payments/types.rs index 13f4de956ec..2d147db2447 100644 --- a/crates/router/src/core/payments/types.rs +++ b/crates/router/src/core/payments/types.rs @@ -370,20 +370,9 @@ impl ForeignTryFrom<&storage::Authentication> for AuthenticationData { type Error = error_stack::Report; fn foreign_try_from(authentication: &storage::Authentication) -> Result { if authentication.authentication_status == common_enums::AuthenticationStatus::Success { - let threeds_server_transaction_id = authentication - .threeds_server_transaction_id - .clone() - .get_required_value("threeds_server_transaction_id") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("threeds_server_transaction_id must not be null when authentication_status is success")?; - let message_version = authentication - .message_version - .clone() - .get_required_value("message_version") - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "message_version must not be null when authentication_status is success", - )?; + let threeds_server_transaction_id = + authentication.threeds_server_transaction_id.clone(); + let message_version = authentication.message_version.clone(); let cavv = authentication .cavv .clone() diff --git a/crates/router/src/core/unified_authentication_service.rs b/crates/router/src/core/unified_authentication_service.rs index 28d32f627cb..63824a40c9a 100644 --- a/crates/router/src/core/unified_authentication_service.rs +++ b/crates/router/src/core/unified_authentication_service.rs @@ -11,7 +11,8 @@ use hyperswitch_domain_models::{ authentication::{MessageCategory, PreAuthenticationData}, unified_authentication_service::{ PaymentDetails, ServiceSessionIds, TransactionDetails, UasAuthenticationRequestData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, BrowserInformation, }, @@ -20,6 +21,7 @@ use hyperswitch_domain_models::{ UasPreAuthenticationRouterData, }, }; +use masking::ExposeInterface; use super::{errors::RouterResult, payments::helpers::MerchantConnectorAccountType}; use crate::{ @@ -152,12 +154,76 @@ impl UnifiedAuthenticationService for ClickToPay { .await } - fn confirmation( - _state: &SessionState, + async fn confirmation( + state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, + payment_data: &PaymentData, + merchant_connector_account: &MerchantConnectorAccountType, + connector_name: &str, + payment_method: common_enums::PaymentMethod, ) -> RouterResult<()> { + let authentication_id = payment_data + .payment_attempt + .authentication_id + .clone() + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("Missing authentication id in payment attempt")?; + + let currency = payment_data.payment_attempt.currency.ok_or( + ApiErrorResponse::MissingRequiredField { + field_name: "currency", + }, + )?; + + let current_time = common_utils::date_time::now(); + + let payment_attempt_status = payment_data.payment_attempt.status; + + let (checkout_event_status, confirmation_reason) = + utils::get_checkout_event_status_and_reason(payment_attempt_status); + + let click_to_pay_details = payment_data.service_details.clone(); + + let authentication_confirmation_data = UasConfirmationRequestData { + x_src_flow_id: payment_data + .service_details + .as_ref() + .and_then(|details| details.x_src_flow_id.clone()), + transaction_amount: payment_data.payment_attempt.net_amount.get_order_amount(), + transaction_currency: currency, + checkout_event_type: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + checkout_event_status: checkout_event_status.clone(), + confirmation_status: checkout_event_status.clone(), + confirmation_reason, + confirmation_timestamp: Some(current_time), + network_authorization_code: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + network_transaction_identifier: Some("01".to_string()), // hardcoded to '01' since only authorise flow is implemented + correlation_id: click_to_pay_details + .clone() + .and_then(|details| details.correlation_id), + merchant_transaction_id: click_to_pay_details + .and_then(|details| details.merchant_transaction_id), + }; + + let authentication_confirmation_router_data : hyperswitch_domain_models::types::UasAuthenticationConfirmationRouterData = utils::construct_uas_router_data( + state, + connector_name.to_string(), + payment_method, + payment_data.payment_attempt.merchant_id.clone(), + None, + authentication_confirmation_data, + merchant_connector_account, + Some(authentication_id.clone()), + )?; + + utils::do_auth_connector_call( + state, + UNIFIED_AUTHENTICATION_SERVICE.to_string(), + authentication_confirmation_router_data, + ) + .await?; + Ok(()) } } @@ -388,17 +454,12 @@ impl UnifiedAuthenticationService for ExternalAuthentication ) .await } - - fn confirmation( - _state: &SessionState, - _key_store: &domain::MerchantKeyStore, - _business_profile: &domain::Profile, - _merchant_connector_account: &MerchantConnectorAccountType, - ) -> RouterResult<()> { - Ok(()) - } } +#[cfg(all( + any(feature = "v1", feature = "v2"), + not(feature = "payment_methods_v2") +))] #[allow(clippy::too_many_arguments)] pub async fn create_new_authentication( state: &SessionState, @@ -410,6 +471,7 @@ pub async fn create_new_authentication( authentication_id: &str, service_details: Option, authentication_status: common_enums::AuthenticationStatus, + network_token: Option, organization_id: common_utils::id_type::OrganizationId, ) -> RouterResult { let service_details_value = service_details @@ -433,10 +495,12 @@ pub async fn create_new_authentication( connector_metadata: None, maximum_supported_version: None, threeds_server_transaction_id: None, - cavv: None, + cavv: network_token + .clone() + .and_then(|data| data.token_cryptogram.map(|cavv| cavv.expose())), authentication_flow_type: None, message_version: None, - eci: None, + eci: network_token.and_then(|data| data.eci), trans_status: None, acquirer_bin: None, acquirer_merchant_id: None, diff --git a/crates/router/src/core/unified_authentication_service/types.rs b/crates/router/src/core/unified_authentication_service/types.rs index bb9abdb1219..7a1caf2d0e7 100644 --- a/crates/router/src/core/unified_authentication_service/types.rs +++ b/crates/router/src/core/unified_authentication_service/types.rs @@ -145,11 +145,14 @@ pub trait UnifiedAuthenticationService { .into()) } - fn confirmation( + async fn confirmation( _state: &SessionState, _key_store: &domain::MerchantKeyStore, _business_profile: &domain::Profile, + _payment_data: &PaymentData, _merchant_connector_account: &MerchantConnectorAccountType, + _connector_name: &str, + _payment_method: common_enums::PaymentMethod, ) -> RouterResult<()> { Err(errors::ApiErrorResponse::NotImplemented { message: NotImplementedMessage::Reason("confirmation".to_string()), diff --git a/crates/router/src/core/unified_authentication_service/utils.rs b/crates/router/src/core/unified_authentication_service/utils.rs index 15ab09e3cdc..b74cd4d0c89 100644 --- a/crates/router/src/core/unified_authentication_service/utils.rs +++ b/crates/router/src/core/unified_authentication_service/utils.rs @@ -136,70 +136,74 @@ pub async fn external_authentication_update_trackers( Ok(response) => match response { UasAuthenticationResponseData::PreAuthentication { authentication_details, - } => diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate { - threeds_server_transaction_id: authentication_details - .threeds_server_transaction_id - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing threeds_server_transaction_id in PreAuthentication Details", - )?, - maximum_supported_3ds_version: authentication_details - .maximum_supported_3ds_version - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing maximum_supported_3ds_version in PreAuthentication Details", - )?, - connector_authentication_id: authentication_details - .connector_authentication_id - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable( - "missing connector_authentication_id in PreAuthentication Details", - )?, - three_ds_method_data: authentication_details.three_ds_method_data, - three_ds_method_url: authentication_details.three_ds_method_url, - message_version: authentication_details - .message_version - .ok_or(ApiErrorResponse::InternalServerError) - .attach_printable("missing message_version in PreAuthentication Details")?, - connector_metadata: authentication_details.connector_metadata, - authentication_status: common_enums::AuthenticationStatus::Pending, - acquirer_bin: acquirer_details - .as_ref() - .map(|acquirer_details| acquirer_details.acquirer_bin.clone()), - acquirer_merchant_id: acquirer_details - .as_ref() - .map(|acquirer_details| acquirer_details.acquirer_merchant_id.clone()), - acquirer_country_code: acquirer_details - .and_then(|acquirer_details| acquirer_details.acquirer_country_code), - directory_server_id: authentication_details.directory_server_id, - }, + } => Ok( + diesel_models::authentication::AuthenticationUpdate::PreAuthenticationUpdate { + threeds_server_transaction_id: authentication_details + .threeds_server_transaction_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing threeds_server_transaction_id in PreAuthentication Details", + )?, + maximum_supported_3ds_version: authentication_details + .maximum_supported_3ds_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing maximum_supported_3ds_version in PreAuthentication Details", + )?, + connector_authentication_id: authentication_details + .connector_authentication_id + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable( + "missing connector_authentication_id in PreAuthentication Details", + )?, + three_ds_method_data: authentication_details.three_ds_method_data, + three_ds_method_url: authentication_details.three_ds_method_url, + message_version: authentication_details + .message_version + .ok_or(ApiErrorResponse::InternalServerError) + .attach_printable("missing message_version in PreAuthentication Details")?, + connector_metadata: authentication_details.connector_metadata, + authentication_status: common_enums::AuthenticationStatus::Pending, + acquirer_bin: acquirer_details + .as_ref() + .map(|acquirer_details| acquirer_details.acquirer_bin.clone()), + acquirer_merchant_id: acquirer_details + .as_ref() + .map(|acquirer_details| acquirer_details.acquirer_merchant_id.clone()), + acquirer_country_code: acquirer_details + .and_then(|acquirer_details| acquirer_details.acquirer_country_code), + directory_server_id: authentication_details.directory_server_id, + }, + ), UasAuthenticationResponseData::Authentication { authentication_details, } => { let authentication_status = common_enums::AuthenticationStatus::foreign_from( authentication_details.trans_status.clone(), ); - diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate { - authentication_value: authentication_details.authentication_value, - trans_status: authentication_details.trans_status, - acs_url: authentication_details.authn_flow_type.get_acs_url(), - challenge_request: authentication_details - .authn_flow_type - .get_challenge_request(), - acs_reference_number: authentication_details - .authn_flow_type - .get_acs_reference_number(), - acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(), - acs_signed_content: authentication_details - .authn_flow_type - .get_acs_signed_content(), - authentication_type: authentication_details - .authn_flow_type - .get_decoupled_authentication_type(), - authentication_status, - connector_metadata: authentication_details.connector_metadata, - ds_trans_id: authentication_details.ds_trans_id, - } + Ok( + diesel_models::authentication::AuthenticationUpdate::AuthenticationUpdate { + authentication_value: authentication_details.authentication_value, + trans_status: authentication_details.trans_status, + acs_url: authentication_details.authn_flow_type.get_acs_url(), + challenge_request: authentication_details + .authn_flow_type + .get_challenge_request(), + acs_reference_number: authentication_details + .authn_flow_type + .get_acs_reference_number(), + acs_trans_id: authentication_details.authn_flow_type.get_acs_trans_id(), + acs_signed_content: authentication_details + .authn_flow_type + .get_acs_signed_content(), + authentication_type: authentication_details + .authn_flow_type + .get_decoupled_authentication_type(), + authentication_status, + connector_metadata: authentication_details.connector_metadata, + ds_trans_id: authentication_details.ds_trans_id, + }, + ) } UasAuthenticationResponseData::PostAuthentication { authentication_details, @@ -208,29 +212,39 @@ pub async fn external_authentication_update_trackers( .trans_status .ok_or(ApiErrorResponse::InternalServerError) .attach_printable("missing trans_status in PostAuthentication Details")?; - diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate { - authentication_status: common_enums::AuthenticationStatus::foreign_from( - trans_status.clone(), - ), - trans_status, - authentication_value: authentication_details - .dynamic_data_details - .and_then(|details| details.dynamic_data_value) - .map(masking::ExposeInterface::expose), - eci: authentication_details.eci, - } + Ok( + diesel_models::authentication::AuthenticationUpdate::PostAuthenticationUpdate { + authentication_status: common_enums::AuthenticationStatus::foreign_from( + trans_status.clone(), + ), + trans_status, + authentication_value: authentication_details + .dynamic_data_details + .and_then(|details| details.dynamic_data_value) + .map(masking::ExposeInterface::expose), + eci: authentication_details.eci, + }, + ) } + + UasAuthenticationResponseData::Confirmation { .. } => Err( + ApiErrorResponse::InternalServerError, + ) + .attach_printable("unexpected api confirmation in external authentication flow."), }, - Err(error) => diesel_models::authentication::AuthenticationUpdate::ErrorUpdate { - connector_authentication_id: error.connector_transaction_id, - authentication_status: common_enums::AuthenticationStatus::Failed, - error_message: error - .reason - .map(|reason| format!("message: {}, reason: {}", error.message, reason)) - .or(Some(error.message)), - error_code: Some(error.code), - }, - }; + Err(error) => Ok( + diesel_models::authentication::AuthenticationUpdate::ErrorUpdate { + connector_authentication_id: error.connector_transaction_id, + authentication_status: common_enums::AuthenticationStatus::Failed, + error_message: error + .reason + .map(|reason| format!("message: {}, reason: {}", error.message, reason)) + .or(Some(error.message)), + error_code: Some(error.code), + }, + ), + }?; + state .store .update_authentication_by_merchant_id_authentication_id( @@ -241,3 +255,18 @@ pub async fn external_authentication_update_trackers( .change_context(ApiErrorResponse::InternalServerError) .attach_printable("Error while updating authentication") } + +pub fn get_checkout_event_status_and_reason( + attempt_status: common_enums::AttemptStatus, +) -> (Option, Option) { + match attempt_status { + common_enums::AttemptStatus::Charged | common_enums::AttemptStatus::Authorized => ( + Some("02".to_string()), + Some("Approval Code received".to_string()), + ), + _ => ( + Some("03".to_string()), + Some("No Approval Code received".to_string()), + ), + } +} diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index a5bdef9572f..7bfbc22d05e 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -59,7 +59,8 @@ pub use hyperswitch_domain_models::{ router_request_types::{ unified_authentication_service::{ UasAuthenticationRequestData, UasAuthenticationResponseData, - UasPostAuthenticationRequestData, UasPreAuthenticationRequestData, + UasConfirmationRequestData, UasPostAuthenticationRequestData, + UasPreAuthenticationRequestData, }, AcceptDisputeRequestData, AccessTokenRequestData, AuthorizeSessionTokenData, BrowserInformation, ChargeRefunds, ChargeRefundsOptions, CompleteAuthorizeData, From d1f537e22909a3580dbf9069852f4257fdbad66b Mon Sep 17 00:00:00 2001 From: Anurag Thakur Date: Thu, 20 Feb 2025 16:39:38 +0530 Subject: [PATCH 119/133] feat(router): Add Payments - List endpoint for v2 (#7191) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- .../api-reference/payments/payments--list.mdx | 3 + api-reference-v2/mint.json | 3 +- api-reference-v2/openapi_spec.json | 667 ++++++++++++++++-- crates/api_models/src/events/payment.rs | 23 +- crates/api_models/src/payments.rs | 295 +++++++- .../src/query/payment_attempt.rs | 58 ++ .../src/payments/payment_attempt.rs | 15 + .../src/payments/payment_intent.rs | 139 ++++ crates/openapi/src/openapi_v2.rs | 7 +- crates/openapi/src/routes/payments.rs | 19 + crates/router/src/core/payments.rs | 76 ++ .../router/src/core/payments/transformers.rs | 48 ++ crates/router/src/db/kafka_store.rs | 69 ++ crates/router/src/routes/app.rs | 1 + crates/router/src/routes/payments.rs | 29 + crates/router/src/types/api/payments.rs | 9 +- .../src/mock_db/payment_attempt.rs | 16 + .../src/mock_db/payment_intent.rs | 29 + .../src/payments/payment_attempt.rs | 66 ++ .../src/payments/payment_intent.rs | 335 +++++++++ 20 files changed, 1816 insertions(+), 91 deletions(-) create mode 100644 api-reference-v2/api-reference/payments/payments--list.mdx diff --git a/api-reference-v2/api-reference/payments/payments--list.mdx b/api-reference-v2/api-reference/payments/payments--list.mdx new file mode 100644 index 00000000000..80350a5705e --- /dev/null +++ b/api-reference-v2/api-reference/payments/payments--list.mdx @@ -0,0 +1,3 @@ +--- +openapi: get /v2/payments/list +--- \ No newline at end of file diff --git a/api-reference-v2/mint.json b/api-reference-v2/mint.json index 8fd3b64230b..d935ec84a61 100644 --- a/api-reference-v2/mint.json +++ b/api-reference-v2/mint.json @@ -43,7 +43,8 @@ "api-reference/payments/payments--payment-methods-list", "api-reference/payments/payments--confirm-intent", "api-reference/payments/payments--get", - "api-reference/payments/payments--create-and-confirm-intent" + "api-reference/payments/payments--create-and-confirm-intent", + "api-reference/payments/payments--list" ] }, { diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index c91810a9902..228aef09dee 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -2258,6 +2258,341 @@ ] } }, + "/v2/payments/list": { + "get": { + "tags": [ + "Payments" + ], + "summary": "Payments - List", + "description": "To list the *payments*", + "operationId": "List all Payments", + "parameters": [ + { + "name": "payment_id", + "in": "path", + "description": "The identifier for payment", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "profile_id", + "in": "path", + "description": "The identifier for business profile", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "customer_id", + "in": "path", + "description": "The identifier for customer", + "required": true, + "schema": { + "type": "string", + "nullable": true, + "maxLength": 64, + "minLength": 1 + }, + "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44" + }, + { + "name": "starting_after", + "in": "path", + "description": "A cursor for use in pagination, fetch the next list after some object", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "ending_before", + "in": "path", + "description": "A cursor for use in pagination, fetch the previous list before some object", + "required": true, + "schema": { + "type": "string", + "nullable": true + }, + "example": "pay_fafa124123" + }, + { + "name": "limit", + "in": "path", + "description": "limit on the number of objects to return", + "required": true, + "schema": { + "type": "integer", + "format": "int32", + "default": 10, + "maximum": 100, + "minimum": 0 + } + }, + { + "name": "offset", + "in": "path", + "description": "The starting point within a list of objects", + "required": true, + "schema": { + "type": "integer", + "format": "int32", + "nullable": true, + "minimum": 0 + } + }, + { + "name": "created", + "in": "path", + "description": "The time at which payment is created", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.lt", + "in": "path", + "description": "Time less than the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.gt", + "in": "path", + "description": "Time greater than the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.lte", + "in": "path", + "description": "Time less than or equals to the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "created.gte", + "in": "path", + "description": "Time greater than or equals to the payment created time", + "required": true, + "schema": { + "type": "string", + "format": "date-time", + "nullable": true + }, + "example": "2022-09-10T10:11:12Z" + }, + { + "name": "start_amount", + "in": "path", + "description": "The start amount to filter list of transactions which are greater than or equal to the start amount", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "end_amount", + "in": "path", + "description": "The end amount to filter list of transactions which are less than or equal to the end amount", + "required": true, + "schema": { + "type": "integer", + "format": "int64", + "nullable": true + } + }, + { + "name": "connector", + "in": "path", + "description": "The connector to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Connector" + } + ], + "nullable": true + } + }, + { + "name": "currency", + "in": "path", + "description": "The currency to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/Currency" + } + ], + "nullable": true + } + }, + { + "name": "status", + "in": "path", + "description": "The payment status to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/IntentStatus" + } + ], + "nullable": true + } + }, + { + "name": "payment_method_type", + "in": "path", + "description": "The payment method type to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethod" + } + ], + "nullable": true + } + }, + { + "name": "payment_method_subtype", + "in": "path", + "description": "The payment method subtype to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + } + }, + { + "name": "authentication_type", + "in": "path", + "description": "The authentication type to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationType" + } + ], + "nullable": true + } + }, + { + "name": "merchant_connector_id", + "in": "path", + "description": "The merchant connector id to filter payments list", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + }, + { + "name": "order_on", + "in": "path", + "description": "The field on which the payments list should be sorted", + "required": true, + "schema": { + "$ref": "#/components/schemas/SortOn" + } + }, + { + "name": "order_by", + "in": "path", + "description": "The order in which payments list should be sorted", + "required": true, + "schema": { + "$ref": "#/components/schemas/SortBy" + } + }, + { + "name": "card_network", + "in": "path", + "description": "The card networks to filter payments list", + "required": true, + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/CardNetwork" + } + ], + "nullable": true + } + }, + { + "name": "merchant_order_reference_id", + "in": "path", + "description": "The identifier for merchant order reference id", + "required": true, + "schema": { + "type": "string", + "nullable": true + } + } + ], + "responses": { + "200": { + "description": "Successfully retrieved a payment list", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/PaymentListResponse" + } + } + } + }, + "404": { + "description": "No payments found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "jwt_key": [] + } + ] + } + }, "/v2/payment-methods": { "post": { "tags": [ @@ -7933,14 +8268,6 @@ "type": "object", "description": "Details of customer attached to this payment", "properties": { - "id": { - "type": "string", - "description": "The identifier for the customer.", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, "name": { "type": "string", "description": "The customer's name", @@ -12348,10 +12675,25 @@ } } }, - "OrderDetailsWithAmount": { + "Order": { "type": "object", "required": [ - "product_name", + "on", + "by" + ], + "properties": { + "on": { + "$ref": "#/components/schemas/SortOn" + }, + "by": { + "$ref": "#/components/schemas/SortBy" + } + } + }, + "OrderDetailsWithAmount": { + "type": "object", + "required": [ + "product_name", "quantity", "amount" ], @@ -13568,74 +13910,32 @@ } } }, - "PaymentListConstraints": { + "PaymentListResponse": { "type": "object", + "required": [ + "count", + "total_count", + "data" + ], "properties": { - "customer_id": { - "type": "string", - "description": "The identifier for customer", - "example": "cus_y3oqhf46pyzuxjbcn2giaqnb44", - "nullable": true, - "maxLength": 64, - "minLength": 1 - }, - "starting_after": { - "type": "string", - "description": "A cursor for use in pagination, fetch the next list after some object", - "example": "pay_fafa124123", - "nullable": true - }, - "ending_before": { - "type": "string", - "description": "A cursor for use in pagination, fetch the previous list before some object", - "example": "pay_fafa124123", - "nullable": true - }, - "limit": { + "count": { "type": "integer", - "format": "int32", - "description": "limit on the number of objects to return", - "default": 10, - "maximum": 100, + "description": "The number of payments included in the current response", "minimum": 0 }, - "created": { - "type": "string", - "format": "date-time", - "description": "The time at which payment is created", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.lt": { - "type": "string", - "format": "date-time", - "description": "Time less than the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.gt": { - "type": "string", - "format": "date-time", - "description": "Time greater than the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true - }, - "created.lte": { - "type": "string", - "format": "date-time", - "description": "Time less than or equals to the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "total_count": { + "type": "integer", + "format": "int64", + "description": "The total number of available payments for given constraints" }, - "created.gte": { - "type": "string", - "format": "date-time", - "description": "Time greater than or equals to the payment created time", - "example": "2022-09-10T10:11:12Z", - "nullable": true + "data": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentsListResponseItem" + }, + "description": "The list of payments response objects" } - }, - "additionalProperties": false + } }, "PaymentMethod": { "type": "string", @@ -15796,6 +16096,217 @@ }, "additionalProperties": false }, + "PaymentsListResponseItem": { + "type": "object", + "required": [ + "id", + "merchant_id", + "profile_id", + "status", + "amount", + "created", + "attempt_count", + "return_url" + ], + "properties": { + "id": { + "type": "string", + "description": "Unique identifier for the payment", + "example": "12345_pay_01926c58bc6e77c09e809964e72af8c8", + "maxLength": 64, + "minLength": 32 + }, + "merchant_id": { + "type": "string", + "description": "This is an identifier for the merchant account. This is inferred from the API key\nprovided during the request", + "example": "merchant_1668273825", + "maxLength": 255 + }, + "profile_id": { + "type": "string", + "description": "The business profile that is associated with this payment" + }, + "customer_id": { + "type": "string", + "description": "The identifier for the customer", + "example": "12345_cus_01926c58bc6e77c09e809964e72af8c8", + "nullable": true, + "maxLength": 64, + "minLength": 32 + }, + "payment_method_id": { + "type": "string", + "description": "Identifier for Payment Method used for the payment", + "nullable": true + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/IntentStatus" + } + ], + "default": "requires_confirmation" + }, + "amount": { + "$ref": "#/components/schemas/PaymentAmountDetailsResponse" + }, + "created": { + "type": "string", + "format": "date-time", + "description": "Time when the payment was created", + "example": "2022-09-10T10:11:12Z" + }, + "payment_method_type": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethod" + } + ], + "nullable": true + }, + "payment_method_subtype": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentMethodType" + } + ], + "nullable": true + }, + "connector": { + "allOf": [ + { + "$ref": "#/components/schemas/Connector" + } + ], + "nullable": true + }, + "merchant_connector_id": { + "type": "string", + "description": "Identifier of the connector ( merchant connector account ) which was chosen to make the payment", + "nullable": true + }, + "customer": { + "allOf": [ + { + "$ref": "#/components/schemas/CustomerDetailsResponse" + } + ], + "nullable": true + }, + "merchant_reference_id": { + "type": "string", + "description": "The reference id for the order in the merchant's system. This value can be passed by the merchant.", + "nullable": true + }, + "connector_payment_id": { + "type": "string", + "description": "A unique identifier for a payment provided by the connector", + "example": "993672945374576J", + "nullable": true + }, + "connector_response_reference_id": { + "type": "string", + "description": "Reference to the capture at connector side", + "nullable": true + }, + "metadata": { + "type": "object", + "description": "Metadata is useful for storing additional, unstructured information on an object.", + "nullable": true + }, + "description": { + "type": "string", + "description": "A description of the payment", + "example": "It's my first payment request", + "nullable": true + }, + "authentication_type": { + "allOf": [ + { + "$ref": "#/components/schemas/AuthenticationType" + } + ], + "default": "three_ds", + "nullable": true + }, + "capture_method": { + "allOf": [ + { + "$ref": "#/components/schemas/CaptureMethod" + } + ], + "nullable": true + }, + "setup_future_usage": { + "allOf": [ + { + "$ref": "#/components/schemas/FutureUsage" + } + ], + "nullable": true + }, + "attempt_count": { + "type": "integer", + "format": "int32", + "description": "Total number of attempts associated with this payment" + }, + "error": { + "allOf": [ + { + "$ref": "#/components/schemas/ErrorDetails" + } + ], + "nullable": true + }, + "cancellation_reason": { + "type": "string", + "description": "If the payment was cancelled the reason will be provided here", + "nullable": true + }, + "order_details": { + "type": "array", + "items": { + "$ref": "#/components/schemas/OrderDetailsWithAmount" + }, + "description": "Information about the product , quantity and amount for connectors. (e.g. Klarna)", + "example": "[{\n \"product_name\": \"gillete creme\",\n \"quantity\": 15,\n \"amount\" : 900\n }]", + "nullable": true + }, + "return_url": { + "type": "string", + "description": "The URL to redirect after the completion of the operation", + "example": "https://hyperswitch.io" + }, + "statement_descriptor": { + "type": "string", + "description": "For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters.", + "example": "Hyperswitch Router", + "nullable": true, + "maxLength": 255 + }, + "allowed_payment_method_types": { + "type": "array", + "items": { + "$ref": "#/components/schemas/PaymentMethodType" + }, + "description": "Allowed Payment Method Types for a given PaymentIntent", + "nullable": true + }, + "authorization_count": { + "type": "integer", + "format": "int32", + "description": "Total number of authorizations happened in an incremental_authorization payment", + "nullable": true + }, + "modified_at": { + "type": "string", + "format": "date-time", + "description": "Date time at which payment was updated", + "example": "2022-09-10T10:11:12Z", + "nullable": true + } + } + }, "PaymentsRequest": { "type": "object", "required": [ @@ -20376,6 +20887,20 @@ "contain" ] }, + "SortBy": { + "type": "string", + "enum": [ + "asc", + "desc" + ] + }, + "SortOn": { + "type": "string", + "enum": [ + "amount", + "created" + ] + }, "SplitPaymentsRequest": { "oneOf": [ { diff --git a/crates/api_models/src/events/payment.rs b/crates/api_models/src/events/payment.rs index 4e672927284..ccda5fd402d 100644 --- a/crates/api_models/src/events/payment.rs +++ b/crates/api_models/src/events/payment.rs @@ -11,7 +11,7 @@ use super::{ ))] use crate::payment_methods::CustomerPaymentMethodsListResponse; #[cfg(feature = "v1")] -use crate::payments::{PaymentListResponse, PaymentListResponseV2}; +use crate::payments::{PaymentListFilterConstraints, PaymentListResponseV2}; #[cfg(all(feature = "v2", feature = "payment_methods_v2"))] use crate::{events, payment_methods::CustomerPaymentMethodsListResponse}; use crate::{ @@ -23,16 +23,15 @@ use crate::{ PaymentMethodUpdate, }, payments::{ - self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, - PaymentListFilterConstraints, PaymentListFilters, PaymentListFiltersV2, - PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, - PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, - PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, - PaymentsExternalAuthenticationRequest, PaymentsExternalAuthenticationResponse, - PaymentsIncrementalAuthorizationRequest, PaymentsManualUpdateRequest, - PaymentsManualUpdateResponse, PaymentsPostSessionTokensRequest, - PaymentsPostSessionTokensResponse, PaymentsRejectRequest, PaymentsResponse, - PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, + self, ExtendedCardInfoResponse, PaymentIdType, PaymentListConstraints, PaymentListFilters, + PaymentListFiltersV2, PaymentListResponse, PaymentsAggregateResponse, + PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, + PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, + PaymentsDynamicTaxCalculationResponse, PaymentsExternalAuthenticationRequest, + PaymentsExternalAuthenticationResponse, PaymentsIncrementalAuthorizationRequest, + PaymentsManualUpdateRequest, PaymentsManualUpdateResponse, + PaymentsPostSessionTokensRequest, PaymentsPostSessionTokensResponse, PaymentsRejectRequest, + PaymentsResponse, PaymentsRetrieveRequest, PaymentsSessionResponse, PaymentsStartRequest, RedirectionResponse, }, }; @@ -350,6 +349,7 @@ impl ApiEventMetric for PaymentMethodCollectLinkResponse { } } +#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListFilterConstraints { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) @@ -373,7 +373,6 @@ impl ApiEventMetric for PaymentListConstraints { } } -#[cfg(feature = "v1")] impl ApiEventMetric for PaymentListResponse { fn get_api_event_type(&self) -> Option { Some(ApiEventsType::ResourceListAPI) diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 7b1ba943ee7..b567bc52286 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -9,8 +9,6 @@ use cards::CardNumber; #[cfg(feature = "v2")] use common_enums::enums::PaymentConnectorTransmission; use common_enums::ProductType; -#[cfg(feature = "v2")] -use common_utils::id_type::GlobalPaymentId; use common_utils::{ consts::default_payments_list_limit, crypto, @@ -101,6 +99,7 @@ pub struct CustomerDetails { pub phone_country_code: Option, } +#[cfg(feature = "v1")] /// Details of customer attached to this payment #[derive( Debug, Default, serde::Serialize, serde::Deserialize, Clone, ToSchema, PartialEq, Setter, @@ -127,6 +126,27 @@ pub struct CustomerDetailsResponse { pub phone_country_code: Option, } +#[cfg(feature = "v2")] +/// Details of customer attached to this payment +#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema, PartialEq, Setter)] +pub struct CustomerDetailsResponse { + /// The customer's name + #[schema(max_length = 255, value_type = Option, example = "John Doe")] + pub name: Option>, + + /// The customer's email address + #[schema(max_length = 255, value_type = Option, example = "johntest@test.com")] + pub email: Option, + + /// The customer's phone number + #[schema(value_type = Option, max_length = 10, example = "9123456789")] + pub phone: Option>, + + /// The country code for the customer's phone number + #[schema(max_length = 2, example = "+1")] + pub phone_country_code: Option, +} + // Serialize is required because the api event requires Serialize to be implemented #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, ToSchema)] #[serde(deny_unknown_fields)] @@ -4892,6 +4912,139 @@ pub struct PaymentsResponse { pub card_discovery: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct PaymentsListResponseItem { + /// Unique identifier for the payment + #[schema( + min_length = 32, + max_length = 64, + example = "12345_pay_01926c58bc6e77c09e809964e72af8c8", + value_type = String, + )] + pub id: id_type::GlobalPaymentId, + + /// This is an identifier for the merchant account. This is inferred from the API key + /// provided during the request + #[schema(max_length = 255, example = "merchant_1668273825", value_type = String)] + pub merchant_id: id_type::MerchantId, + + /// The business profile that is associated with this payment + #[schema(value_type = String)] + pub profile_id: id_type::ProfileId, + + /// The identifier for the customer + #[schema( + min_length = 32, + max_length = 64, + example = "12345_cus_01926c58bc6e77c09e809964e72af8c8", + value_type = Option + )] + pub customer_id: Option, + + /// Identifier for Payment Method used for the payment + #[schema(value_type = Option)] + pub payment_method_id: Option, + + /// Status of the payment + #[schema(value_type = IntentStatus, example = "failed", default = "requires_confirmation")] + pub status: api_enums::IntentStatus, + + /// Amount related information for this payment and attempt + pub amount: PaymentAmountDetailsResponse, + + /// Time when the payment was created + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(with = "common_utils::custom_serde::iso8601")] + pub created: PrimitiveDateTime, + + /// The payment method type for this payment attempt + #[schema(value_type = Option, example = "wallet")] + pub payment_method_type: Option, + + #[schema(value_type = Option, example = "apple_pay")] + pub payment_method_subtype: Option, + + /// The connector used for the payment + #[schema(value_type = Option, example = "stripe")] + pub connector: Option, + + /// Identifier of the connector ( merchant connector account ) which was chosen to make the payment + #[schema(value_type = Option)] + pub merchant_connector_id: Option, + + /// Details of the customer + pub customer: Option, + + /// The reference id for the order in the merchant's system. This value can be passed by the merchant. + #[schema(value_type = Option)] + pub merchant_reference_id: Option, + + /// A unique identifier for a payment provided by the connector + #[schema(value_type = Option, example = "993672945374576J")] + pub connector_payment_id: Option, + + /// Reference to the capture at connector side + pub connector_response_reference_id: Option, + + /// Metadata is useful for storing additional, unstructured information on an object. + #[schema(value_type = Option, example = r#"{ "udf1": "some-value", "udf2": "some-value" }"#)] + pub metadata: Option>, + + /// A description of the payment + #[schema(example = "It's my first payment request")] + pub description: Option, + + /// The transaction authentication can be set to undergo payer authentication. By default, the authentication will be marked as NO_THREE_DS + #[schema(value_type = Option, example = "no_three_ds", default = "three_ds")] + pub authentication_type: Option, + + /// This is the instruction for capture/ debit the money from the users' card. On the other hand authorization refers to blocking the amount on the users' payment method. + #[schema(value_type = Option, example = "automatic")] + pub capture_method: Option, + + /// Indicates that you intend to make future payments with this Payment’s payment method. Providing this parameter will attach the payment method to the Customer, if present, after the Payment is confirmed and any required actions from the user are complete. + #[schema(value_type = Option, example = "off_session")] + pub setup_future_usage: Option, + + /// Total number of attempts associated with this payment + pub attempt_count: i16, + + /// Error details for the payment if any + pub error: Option, + + /// If the payment was cancelled the reason will be provided here + pub cancellation_reason: Option, + + /// Information about the product , quantity and amount for connectors. (e.g. Klarna) + #[schema(value_type = Option>, example = r#"[{ + "product_name": "gillete creme", + "quantity": 15, + "amount" : 900 + }]"#)] + pub order_details: Option>>, + + /// The URL to redirect after the completion of the operation + #[schema(value_type = String, example = "https://hyperswitch.io")] + pub return_url: Option, + + /// For non-card charges, you can use this value as the complete description that appears on your customers’ statements. Must contain at least one letter, maximum 22 characters. + #[schema(value_type = Option, max_length = 255, example = "Hyperswitch Router")] + pub statement_descriptor: Option, + + /// Allowed Payment Method Types for a given PaymentIntent + #[schema(value_type = Option>)] + pub allowed_payment_method_types: Option>, + + /// Total number of authorizations happened in an incremental_authorization payment + pub authorization_count: Option, + + /// Date time at which payment was updated + #[schema(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub modified_at: Option, +} + // Serialize is implemented because, this will be serialized in the api events. // Usually request types should not have serialize implemented. // @@ -5447,6 +5600,7 @@ pub struct ExternalAuthenticationDetailsResponse { pub error_message: Option, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Deserialize, ToSchema, serde::Serialize)] #[serde(deny_unknown_fields)] pub struct PaymentListConstraints { @@ -5511,6 +5665,131 @@ pub struct PaymentListConstraints { pub created_gte: Option, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, utoipa::IntoParams)] +#[serde(deny_unknown_fields)] +pub struct PaymentListConstraints { + /// The identifier for payment + #[param(example = "pay_fafa124123", value_type = Option)] + pub payment_id: Option, + + /// The identifier for business profile + #[param(example = "pay_fafa124123", value_type = Option)] + pub profile_id: Option, + + /// The identifier for customer + #[param( + max_length = 64, + min_length = 1, + example = "cus_y3oqhf46pyzuxjbcn2giaqnb44", + value_type = Option, + )] + pub customer_id: Option, + + /// A cursor for use in pagination, fetch the next list after some object + #[param(example = "pay_fafa124123", value_type = Option)] + pub starting_after: Option, + + /// A cursor for use in pagination, fetch the previous list before some object + #[param(example = "pay_fafa124123", value_type = Option)] + pub ending_before: Option, + + /// limit on the number of objects to return + #[param(default = 10, maximum = 100)] + #[serde(default = "default_payments_list_limit")] + pub limit: u32, + + /// The starting point within a list of objects + pub offset: Option, + + /// The time at which payment is created + #[param(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + pub created: Option, + + /// Time less than the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.lt" + )] + pub created_lt: Option, + + /// Time greater than the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.gt" + )] + pub created_gt: Option, + + /// Time less than or equals to the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde( + default, + with = "common_utils::custom_serde::iso8601::option", + rename = "created.lte" + )] + pub created_lte: Option, + + /// Time greater than or equals to the payment created time + #[param(example = "2022-09-10T10:11:12Z")] + #[serde(default, with = "common_utils::custom_serde::iso8601::option")] + #[serde(rename = "created.gte")] + pub created_gte: Option, + + /// The start amount to filter list of transactions which are greater than or equal to the start amount + pub start_amount: Option, + /// The end amount to filter list of transactions which are less than or equal to the end amount + pub end_amount: Option, + /// The connector to filter payments list + #[param(value_type = Option)] + pub connector: Option, + /// The currency to filter payments list + #[param(value_type = Option)] + pub currency: Option, + /// The payment status to filter payments list + #[param(value_type = Option)] + pub status: Option, + /// The payment method type to filter payments list + #[param(value_type = Option)] + pub payment_method_type: Option, + /// The payment method subtype to filter payments list + #[param(value_type = Option)] + pub payment_method_subtype: Option, + /// The authentication type to filter payments list + #[param(value_type = Option)] + pub authentication_type: Option, + /// The merchant connector id to filter payments list + #[param(value_type = Option)] + pub merchant_connector_id: Option, + /// The field on which the payments list should be sorted + #[serde(default)] + pub order_on: SortOn, + /// The order in which payments list should be sorted + #[serde(default)] + pub order_by: SortBy, + /// The card networks to filter payments list + #[param(value_type = Option)] + pub card_network: Option, + /// The identifier for merchant order reference id + pub merchant_order_reference_id: Option, +} + +#[cfg(feature = "v2")] +impl PaymentListConstraints { + pub fn has_no_attempt_filters(&self) -> bool { + self.connector.is_none() + && self.payment_method_type.is_none() + && self.payment_method_subtype.is_none() + && self.authentication_type.is_none() + && self.merchant_connector_id.is_none() + && self.card_network.is_none() + } +} + #[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Serialize, ToSchema)] pub struct PaymentListResponse { @@ -5520,6 +5799,16 @@ pub struct PaymentListResponse { pub data: Vec, } +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Serialize, ToSchema)] +pub struct PaymentListResponse { + /// The number of payments included in the current response + pub count: usize, + /// The total number of available payments for given constraints + pub total_count: i64, + /// The list of payments response objects + pub data: Vec, +} #[derive(Setter, Clone, Default, Debug, PartialEq, serde::Serialize, ToSchema)] pub struct IncrementalAuthorizationResponse { /// The unique identifier of authorization @@ -5549,6 +5838,7 @@ pub struct PaymentListResponseV2 { pub data: Vec, } +#[cfg(feature = "v1")] #[derive(Clone, Debug, serde::Deserialize, serde::Serialize)] pub struct PaymentListFilterConstraints { /// The identifier for payment @@ -5592,6 +5882,7 @@ pub struct PaymentListFilterConstraints { pub card_discovery: Option>, } +#[cfg(feature = "v1")] impl PaymentListFilterConstraints { pub fn has_no_attempt_filters(&self) -> bool { self.connector.is_none() diff --git a/crates/diesel_models/src/query/payment_attempt.rs b/crates/diesel_models/src/query/payment_attempt.rs index 60393211968..cfcbc2bd199 100644 --- a/crates/diesel_models/src/query/payment_attempt.rs +++ b/crates/diesel_models/src/query/payment_attempt.rs @@ -412,6 +412,64 @@ impl PaymentAttempt { )) } + #[cfg(feature = "v2")] + #[allow(clippy::too_many_arguments)] + pub async fn get_total_count_of_attempts( + conn: &PgPooledConn, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + ) -> StorageResult { + let mut filter = ::table() + .count() + .filter(dsl::merchant_id.eq(merchant_id.to_owned())) + .filter(dsl::id.eq_any(active_attempt_ids.to_owned())) + .into_boxed(); + + if let Some(connector) = connector { + filter = filter.filter(dsl::connector.eq(connector)); + } + + if let Some(payment_method_type) = payment_method_type { + filter = filter.filter(dsl::payment_method_type_v2.eq(payment_method_type)); + } + if let Some(payment_method_subtype) = payment_method_subtype { + filter = filter.filter(dsl::payment_method_subtype.eq(payment_method_subtype)); + } + if let Some(authentication_type) = authentication_type { + filter = filter.filter(dsl::authentication_type.eq(authentication_type)); + } + if let Some(merchant_connector_id) = merchant_connector_id { + filter = filter.filter(dsl::merchant_connector_id.eq(merchant_connector_id)) + } + if let Some(card_network) = card_network { + filter = filter.filter(dsl::card_network.eq(card_network)) + } + + router_env::logger::debug!(query = %debug_query::(&filter).to_string()); + + // TODO: Remove these logs after debugging the issue for delay in count query + let start_time = std::time::Instant::now(); + router_env::logger::debug!("Executing count query start_time: {:?}", start_time); + let result = db_metrics::track_database_call::<::Table, _, _>( + filter.get_result_async::(conn), + db_metrics::DatabaseOperation::Filter, + ) + .await + .change_context(DatabaseError::Others) + .attach_printable("Error filtering count of payments"); + + let duration = start_time.elapsed(); + router_env::logger::debug!("Completed count query in {:?}", duration); + + result + } + #[cfg(feature = "v1")] #[allow(clippy::too_many_arguments)] pub async fn get_total_count_of_attempts( diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index f557e7c907b..dfce21f28b6 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -200,6 +200,21 @@ pub trait PaymentAttemptInterface { card_discovery: Option>, storage_scheme: storage_enums::MerchantStorageScheme, ) -> error_stack::Result; + + #[cfg(all(feature = "v2", feature = "olap"))] + #[allow(clippy::too_many_arguments)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result; } #[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)] diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index f82eb782c50..9d4debacaba 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -120,6 +120,30 @@ pub trait PaymentIntentInterface { storage_scheme: common_enums::MerchantStorageScheme, ) -> error_stack::Result, errors::StorageError>; + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result< + Vec<( + PaymentIntent, + Option, + )>, + errors::StorageError, + >; + + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + storage_scheme: common_enums::MerchantStorageScheme, + ) -> error_stack::Result>, errors::StorageError>; + #[cfg(all(feature = "v1", feature = "olap"))] async fn get_filtered_active_attempt_ids_for_total_count( &self, @@ -1077,6 +1101,7 @@ impl From for diesel_models::PaymentIntentUpdateInt } } +#[cfg(feature = "v1")] pub enum PaymentIntentFetchConstraints { Single { payment_intent_id: id_type::PaymentId, @@ -1084,6 +1109,7 @@ pub enum PaymentIntentFetchConstraints { List(Box), } +#[cfg(feature = "v1")] impl PaymentIntentFetchConstraints { pub fn get_profile_id_list(&self) -> Option> { if let Self::List(pi_list_params) = self { @@ -1094,6 +1120,26 @@ impl PaymentIntentFetchConstraints { } } +#[cfg(feature = "v2")] +pub enum PaymentIntentFetchConstraints { + Single { + payment_intent_id: id_type::GlobalPaymentId, + }, + List(Box), +} + +#[cfg(feature = "v2")] +impl PaymentIntentFetchConstraints { + pub fn get_profile_id(&self) -> Option { + if let Self::List(pi_list_params) = self { + pi_list_params.profile_id.clone() + } else { + None + } + } +} + +#[cfg(feature = "v1")] pub struct PaymentIntentListParams { pub offset: u32, pub starting_at: Option, @@ -1117,6 +1163,30 @@ pub struct PaymentIntentListParams { pub merchant_order_reference_id: Option, } +#[cfg(feature = "v2")] +pub struct PaymentIntentListParams { + pub offset: u32, + pub starting_at: Option, + pub ending_at: Option, + pub amount_filter: Option, + pub connector: Option, + pub currency: Option, + pub status: Option, + pub payment_method_type: Option, + pub payment_method_subtype: Option, + pub authentication_type: Option, + pub merchant_connector_id: Option, + pub profile_id: Option, + pub customer_id: Option, + pub starting_after_id: Option, + pub ending_before_id: Option, + pub limit: Option, + pub order: api_models::payments::Order, + pub card_network: Option, + pub merchant_order_reference_id: Option, +} + +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListConstraints) -> Self { let api_models::payments::PaymentListConstraints { @@ -1155,6 +1225,73 @@ impl From for PaymentIntentFetchCo } } +#[cfg(feature = "v2")] +impl From for PaymentIntentFetchConstraints { + fn from(value: api_models::payments::PaymentListConstraints) -> Self { + let api_models::payments::PaymentListConstraints { + customer_id, + starting_after, + ending_before, + limit, + created, + created_lt, + created_gt, + created_lte, + created_gte, + payment_id, + profile_id, + start_amount, + end_amount, + connector, + currency, + status, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + order_on, + order_by, + card_network, + merchant_order_reference_id, + offset, + } = value; + if let Some(payment_intent_id) = payment_id { + Self::Single { payment_intent_id } + } else { + Self::List(Box::new(PaymentIntentListParams { + offset: offset.unwrap_or_default(), + starting_at: created_gte.or(created_gt).or(created), + ending_at: created_lte.or(created_lt).or(created), + amount_filter: (start_amount.is_some() || end_amount.is_some()).then_some({ + api_models::payments::AmountFilter { + start_amount, + end_amount, + } + }), + connector, + currency, + status, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + profile_id, + customer_id, + starting_after_id: starting_after, + ending_before_id: ending_before, + limit: Some(std::cmp::min(limit, PAYMENTS_LIST_MAX_LIMIT_V1)), + order: api_models::payments::Order { + on: order_on, + by: order_by, + }, + card_network, + merchant_order_reference_id, + })) + } + } +} + +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: common_utils::types::TimeRange) -> Self { Self::List(Box::new(PaymentIntentListParams { @@ -1182,6 +1319,7 @@ impl From for PaymentIntentFetchConstraints { } } +#[cfg(feature = "v1")] impl From for PaymentIntentFetchConstraints { fn from(value: api_models::payments::PaymentListFilterConstraints) -> Self { let api_models::payments::PaymentListFilterConstraints { @@ -1233,6 +1371,7 @@ impl From for PaymentIntentF } } +#[cfg(feature = "v1")] impl TryFrom<(T, Option>)> for PaymentIntentFetchConstraints where Self: From, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index e5c9c6fa81f..12e42320b40 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -127,6 +127,7 @@ Never share your secret api keys. Keep them guarded and secure. routes::payments::payments_create_and_confirm_intent, routes::payments::payments_connector_session, routes::payments::list_payment_methods, + routes::payments::payments_list, //Routes for payment methods routes::payment_method::create_payment_method_api, @@ -375,6 +376,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::ConnectorTokenDetails, api_models::payments::PaymentsRequest, api_models::payments::PaymentsResponse, + api_models::payments::PaymentsListResponseItem, api_models::payments::PaymentRetrieveBody, api_models::payments::PaymentsRetrieveRequest, api_models::payments::PaymentsCaptureRequest, @@ -437,7 +439,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::SamsungPayCardBrand, api_models::payments::SamsungPayTokenData, api_models::payments::PaymentsCancelRequest, - api_models::payments::PaymentListConstraints, + api_models::payments::PaymentListResponse, api_models::payments::CashappQr, api_models::payments::BankTransferData, api_models::payments::BankTransferNextStepsData, @@ -493,6 +495,9 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentsConfirmIntentResponse, api_models::payments::AmountDetailsResponse, api_models::payments::BankCodeResponse, + api_models::payments::Order, + api_models::payments::SortOn, + api_models::payments::SortBy, api_models::payments::PaymentMethodListResponseForPayments, api_models::payments::ResponsePaymentMethodTypesForPayments, api_models::payment_methods::RequiredFieldInfo, diff --git a/crates/openapi/src/routes/payments.rs b/crates/openapi/src/routes/payments.rs index ceccd53c4dd..633e0685e60 100644 --- a/crates/openapi/src/routes/payments.rs +++ b/crates/openapi/src/routes/payments.rs @@ -457,6 +457,7 @@ pub fn payments_cancel() {} /// Payments - List /// /// To list the *payments* +#[cfg(feature = "v1")] #[utoipa::path( get, path = "/payments/list", @@ -846,3 +847,21 @@ pub(crate) enum ForceSync { security(("publishable_key" = [])) )] pub fn list_payment_methods() {} + +/// Payments - List +/// +/// To list the *payments* +#[cfg(feature = "v2")] +#[utoipa::path( + get, + path = "/v2/payments/list", + params(api_models::payments::PaymentListConstraints), + responses( + (status = 200, description = "Successfully retrieved a payment list", body = PaymentListResponse), + (status = 404, description = "No payments found") + ), + tag = "Payments", + operation_id = "List all Payments", + security(("api_key" = []), ("jwt_key" = [])) +)] +pub fn payments_list() {} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index a14d8b14df8..d97a52ab55b 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -5252,6 +5252,82 @@ pub async fn list_payments( )) } +#[cfg(all(feature = "v2", feature = "olap"))] +pub async fn list_payments( + state: SessionState, + merchant: domain::MerchantAccount, + key_store: domain::MerchantKeyStore, + constraints: api::PaymentListConstraints, +) -> RouterResponse { + common_utils::metrics::utils::record_operation_time( + async { + let limit = &constraints.limit; + helpers::validate_payment_list_request_for_joins(*limit)?; + let db: &dyn StorageInterface = state.store.as_ref(); + let fetch_constraints = constraints.clone().into(); + let list: Vec<(storage::PaymentIntent, Option)> = db + .get_filtered_payment_intents_attempt( + &(&state).into(), + merchant.get_id(), + &fetch_constraints, + &key_store, + merchant.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::PaymentNotFound)?; + let data: Vec = + list.into_iter().map(ForeignFrom::foreign_from).collect(); + + let active_attempt_ids = db + .get_filtered_active_attempt_ids_for_total_count( + merchant.get_id(), + &fetch_constraints, + merchant.storage_scheme, + ) + .await + .to_not_found_response(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while retrieving active_attempt_ids for merchant")?; + + let total_count = if constraints.has_no_attempt_filters() { + i64::try_from(active_attempt_ids.len()) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while converting from usize to i64") + } else { + let active_attempt_ids = active_attempt_ids + .into_iter() + .flatten() + .collect::>(); + + db.get_total_count_of_filtered_payment_attempts( + merchant.get_id(), + &active_attempt_ids, + constraints.connector, + constraints.payment_method_type, + constraints.payment_method_subtype, + constraints.authentication_type, + constraints.merchant_connector_id, + constraints.card_network, + merchant.storage_scheme, + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Error while retrieving total count of payment attempts") + }?; + + Ok(services::ApplicationResponse::Json( + api_models::payments::PaymentListResponse { + count: data.len(), + total_count, + data, + }, + )) + }, + &metrics::PAYMENT_LIST_LATENCY, + router_env::metric_attributes!(("merchant_id", merchant.get_id().clone())), + ) + .await +} + #[cfg(all(feature = "olap", feature = "v1"))] pub async fn apply_filters_on_payments( state: SessionState, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5f39ad71352..630f6176726 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -2774,6 +2774,54 @@ impl ForeignFrom<(storage::PaymentIntent, storage::PaymentAttempt)> for api::Pay } } +#[cfg(feature = "v2")] +impl ForeignFrom<(storage::PaymentIntent, Option)> + for api_models::payments::PaymentsListResponseItem +{ + fn foreign_from((pi, pa): (storage::PaymentIntent, Option)) -> Self { + Self { + id: pi.id, + merchant_id: pi.merchant_id, + profile_id: pi.profile_id, + customer_id: pi.customer_id, + payment_method_id: pa.as_ref().and_then(|p| p.payment_method_id.clone()), + status: pi.status, + amount: api_models::payments::PaymentAmountDetailsResponse::foreign_from(( + &pi.amount_details, + pa.as_ref().map(|p| &p.amount_details), + )), + created: pi.created_at, + payment_method_type: pa.as_ref().and_then(|p| p.payment_method_type.into()), + payment_method_subtype: pa.as_ref().and_then(|p| p.payment_method_subtype.into()), + connector: pa.as_ref().and_then(|p| p.connector.clone()), + merchant_connector_id: pa.as_ref().and_then(|p| p.merchant_connector_id.clone()), + customer: None, + merchant_reference_id: pi.merchant_reference_id, + connector_payment_id: pa.as_ref().and_then(|p| p.connector_payment_id.clone()), + connector_response_reference_id: pa + .as_ref() + .and_then(|p| p.connector_response_reference_id.clone()), + metadata: pi.metadata, + description: pi.description.map(|val| val.get_string_repr().to_string()), + authentication_type: pi.authentication_type, + capture_method: Some(pi.capture_method), + setup_future_usage: Some(pi.setup_future_usage), + attempt_count: pi.attempt_count, + error: pa + .as_ref() + .and_then(|p| p.error.as_ref()) + .map(|e| api_models::payments::ErrorDetails::foreign_from(e.clone())), + cancellation_reason: pa.as_ref().and_then(|p| p.cancellation_reason.clone()), + order_details: None, + return_url: pi.return_url, + statement_descriptor: pi.statement_descriptor, + allowed_payment_method_types: pi.allowed_payment_method_types, + authorization_count: pi.authorization_count, + modified_at: pa.as_ref().map(|p| p.modified_at), + } + } +} + #[cfg(feature = "v1")] impl ForeignFrom for api::ephemeral_key::EphemeralKeyCreateResponse { fn foreign_from(from: ephemeral_key::EphemeralKey) -> Self { diff --git a/crates/router/src/db/kafka_store.rs b/crates/router/src/db/kafka_store.rs index d21c345b5fe..a8189468b16 100644 --- a/crates/router/src/db/kafka_store.rs +++ b/crates/router/src/db/kafka_store.rs @@ -1735,6 +1735,34 @@ impl PaymentAttemptInterface for KafkaStore { .await } + #[cfg(feature = "v2")] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.diesel_store + .get_total_count_of_filtered_payment_attempts( + merchant_id, + active_attempt_ids, + connector, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] async fn find_attempts_by_merchant_id_payment_id( &self, @@ -1914,6 +1942,32 @@ impl PaymentIntentInterface for KafkaStore { .await } + #[cfg(all(feature = "olap", feature = "v2"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + key_store: &domain::MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult< + Vec<( + hyperswitch_domain_models::payments::PaymentIntent, + Option, + )>, + errors::DataStorageError, + > { + self.diesel_store + .get_filtered_payment_intents_attempt( + state, + merchant_id, + constraints, + key_store, + storage_scheme, + ) + .await + } + #[cfg(all(feature = "olap", feature = "v1"))] async fn get_filtered_active_attempt_ids_for_total_count( &self, @@ -1952,6 +2006,21 @@ impl PaymentIntentInterface for KafkaStore { ) .await } + #[cfg(all(feature = "olap", feature = "v2"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult>, errors::DataStorageError> { + self.diesel_store + .get_filtered_active_attempt_ids_for_total_count( + merchant_id, + constraints, + storage_scheme, + ) + .await + } } #[async_trait::async_trait] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index 14372cbfbba..b8acc66d522 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -573,6 +573,7 @@ impl Payments { web::resource("/create-intent") .route(web::post().to(payments::payments_create_intent)), ) + .service(web::resource("/list").route(web::get().to(payments::payments_list))) .service( web::resource("/aggregate").route(web::get().to(payments::get_payments_aggregates)), ) diff --git a/crates/router/src/routes/payments.rs b/crates/router/src/routes/payments.rs index 7fa38d6cbb2..870bd63f010 100644 --- a/crates/router/src/routes/payments.rs +++ b/crates/router/src/routes/payments.rs @@ -1234,6 +1234,35 @@ pub async fn payments_list( .await } +#[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] +#[cfg(all(feature = "olap", feature = "v2"))] +pub async fn payments_list( + state: web::Data, + req: actix_web::HttpRequest, + payload: web::Query, +) -> impl Responder { + let flow = Flow::PaymentsList; + let payload = payload.into_inner(); + Box::pin(api::server_wrap( + flow, + state, + &req, + payload, + |state, auth: auth::AuthenticationData, req, _| { + payments::list_payments(state, auth.merchant_account, auth.key_store, req) + }, + auth::auth_type( + &auth::HeaderAuth(auth::ApiKeyAuth), + &auth::JWTAuth { + permission: Permission::MerchantPaymentRead, + }, + req.headers(), + ), + api_locking::LockAction::NotApplicable, + )) + .await +} + #[instrument(skip_all, fields(flow = ?Flow::PaymentsList))] #[cfg(all(feature = "olap", feature = "v1"))] pub async fn profile_payments_list( diff --git a/crates/router/src/types/api/payments.rs b/crates/router/src/types/api/payments.rs index 58159fc5688..74329844a76 100644 --- a/crates/router/src/types/api/payments.rs +++ b/crates/router/src/types/api/payments.rs @@ -1,5 +1,7 @@ #[cfg(feature = "v1")] -pub use api_models::payments::{PaymentListResponse, PaymentListResponseV2}; +pub use api_models::payments::{ + PaymentListFilterConstraints, PaymentListResponse, PaymentListResponseV2, +}; #[cfg(feature = "v2")] pub use api_models::payments::{ PaymentsConfirmIntentRequest, PaymentsCreateIntentRequest, PaymentsIntentResponse, @@ -14,9 +16,8 @@ pub use api_models::{ CryptoData, CustomerAcceptance, CustomerDetailsResponse, MandateAmountData, MandateData, MandateTransactionType, MandateType, MandateValidationFields, NextActionType, OnlineMandate, OpenBankingSessionToken, PayLaterData, PaymentIdType, - PaymentListConstraints, PaymentListFilterConstraints, PaymentListFilters, - PaymentListFiltersV2, PaymentMethodData, PaymentMethodDataRequest, - PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, + PaymentListConstraints, PaymentListFilters, PaymentListFiltersV2, PaymentMethodData, + PaymentMethodDataRequest, PaymentMethodDataResponse, PaymentOp, PaymentRetrieveBody, PaymentRetrieveBodyWithCredentials, PaymentsAggregateResponse, PaymentsApproveRequest, PaymentsCancelRequest, PaymentsCaptureRequest, PaymentsCompleteAuthorizeRequest, PaymentsDynamicTaxCalculationRequest, PaymentsDynamicTaxCalculationResponse, diff --git a/crates/storage_impl/src/mock_db/payment_attempt.rs b/crates/storage_impl/src/mock_db/payment_attempt.rs index a44624203bb..1e4efb2b350 100644 --- a/crates/storage_impl/src/mock_db/payment_attempt.rs +++ b/crates/storage_impl/src/mock_db/payment_attempt.rs @@ -59,6 +59,22 @@ impl PaymentAttemptInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_total_count_of_filtered_payment_attempts( + &self, + _merchant_id: &id_type::MerchantId, + _active_attempt_ids: &[String], + _connector: Option, + _payment_method_type: Option, + _payment_method_subtype: Option, + _authentication_type: Option, + _merchanat_connector_id: Option, + _card_network: Option, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> CustomResult { + Err(StorageError::MockDbError)? + } + #[cfg(feature = "v1")] async fn find_payment_attempt_by_attempt_id_merchant_id( &self, diff --git a/crates/storage_impl/src/mock_db/payment_intent.rs b/crates/storage_impl/src/mock_db/payment_intent.rs index 6a46702e525..3c90ecbdc7f 100644 --- a/crates/storage_impl/src/mock_db/payment_intent.rs +++ b/crates/storage_impl/src/mock_db/payment_intent.rs @@ -28,6 +28,24 @@ impl PaymentIntentInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result< + Vec<( + PaymentIntent, + Option, + )>, + StorageError, + > { + Err(StorageError::MockDbError)? + } + #[cfg(all(feature = "v1", feature = "olap"))] async fn filter_payment_intents_by_time_range_constraints( &self, @@ -63,6 +81,17 @@ impl PaymentIntentInterface for MockDb { Err(StorageError::MockDbError)? } + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + _merchant_id: &common_utils::id_type::MerchantId, + _constraints: &hyperswitch_domain_models::payments::payment_intent::PaymentIntentFetchConstraints, + _storage_scheme: storage_enums::MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + // [#172]: Implement function for `MockDb` + Err(StorageError::MockDbError)? + } + #[cfg(all(feature = "v1", feature = "olap"))] async fn get_filtered_payment_intents_attempt( &self, diff --git a/crates/storage_impl/src/payments/payment_attempt.rs b/crates/storage_impl/src/payments/payment_attempt.rs index 98569ea69ae..96643cefc06 100644 --- a/crates/storage_impl/src/payments/payment_attempt.rs +++ b/crates/storage_impl/src/payments/payment_attempt.rs @@ -507,6 +507,44 @@ impl PaymentAttemptInterface for RouterStore { er.change_context(new_err) }) } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + _storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + let conn = self + .db_store + .get_replica_pool() + .get() + .await + .change_context(errors::StorageError::DatabaseConnectionError)?; + + DieselPaymentAttempt::get_total_count_of_attempts( + &conn, + merchant_id, + active_attempt_ids, + connector.map(|val| val.to_string()), + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + ) + .await + .map_err(|er| { + let new_err = diesel_error_to_data_error(*er.current_context()); + er.change_context(new_err) + }) + } } #[async_trait::async_trait] @@ -1427,6 +1465,34 @@ impl PaymentAttemptInterface for KVRouterStore { ) .await } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_total_count_of_filtered_payment_attempts( + &self, + merchant_id: &common_utils::id_type::MerchantId, + active_attempt_ids: &[String], + connector: Option, + payment_method_type: Option, + payment_method_subtype: Option, + authentication_type: Option, + merchant_connector_id: Option, + card_network: Option, + storage_scheme: MerchantStorageScheme, + ) -> CustomResult { + self.router_store + .get_total_count_of_filtered_payment_attempts( + merchant_id, + active_attempt_ids, + connector, + payment_method_type, + payment_method_subtype, + authentication_type, + merchant_connector_id, + card_network, + storage_scheme, + ) + .await + } } impl DataModelExt for MandateAmountData { diff --git a/crates/storage_impl/src/payments/payment_intent.rs b/crates/storage_impl/src/payments/payment_intent.rs index b64d1aee364..e53a72c8734 100644 --- a/crates/storage_impl/src/payments/payment_intent.rs +++ b/crates/storage_impl/src/payments/payment_intent.rs @@ -164,6 +164,27 @@ impl PaymentIntentInterface for KVRouterStore { } } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result)>, StorageError> { + self.router_store + .get_filtered_payment_intents_attempt( + state, + merchant_id, + constraints, + merchant_key_store, + storage_scheme, + ) + .await + } + #[cfg(feature = "v1")] #[instrument(skip_all)] async fn update_payment_intent( @@ -459,6 +480,23 @@ impl PaymentIntentInterface for KVRouterStore { ) .await } + + #[cfg(all(feature = "v2", feature = "olap"))] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + self.router_store + .get_filtered_active_attempt_ids_for_total_count( + merchant_id, + constraints, + storage_scheme, + ) + .await + } + #[cfg(feature = "v2")] async fn find_payment_intent_by_merchant_reference_id_profile_id( &self, @@ -1086,6 +1124,303 @@ impl PaymentIntentInterface for crate::RouterStore { .await } + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_payment_intents_attempt( + &self, + state: &KeyManagerState, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + merchant_key_store: &MerchantKeyStore, + storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result)>, StorageError> { + use diesel::NullableExpressionMethods as _; + use futures::{future::try_join_all, FutureExt}; + + use crate::DataModelExt; + + let conn = connection::pg_connection_read(self).await.switch()?; + let conn = async_bb8_diesel::Connection::as_async_conn(&conn); + let mut query = DieselPaymentIntent::table() + .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) + .left_join( + payment_attempt_schema::table + .on(pi_dsl::active_attempt_id.eq(pa_dsl::id.nullable())), + ) + // Filtering on merchant_id for payment_attempt is not required for v2 as payment_attempt_ids are globally unique + .into_boxed(); + + query = match constraints { + PaymentIntentFetchConstraints::Single { payment_intent_id } => { + query.filter(pi_dsl::id.eq(payment_intent_id.to_owned())) + } + PaymentIntentFetchConstraints::List(params) => { + query = match params.order { + Order { + on: SortOn::Amount, + by: SortBy::Asc, + } => query.order(pi_dsl::amount.asc()), + Order { + on: SortOn::Amount, + by: SortBy::Desc, + } => query.order(pi_dsl::amount.desc()), + Order { + on: SortOn::Created, + by: SortBy::Asc, + } => query.order(pi_dsl::created_at.asc()), + Order { + on: SortOn::Created, + by: SortBy::Desc, + } => query.order(pi_dsl::created_at.desc()), + }; + + if let Some(limit) = params.limit { + query = query.limit(limit.into()); + } + + if let Some(customer_id) = ¶ms.customer_id { + query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); + } + + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } + + query = match (params.starting_at, ¶ms.starting_after_id) { + (Some(starting_at), _) => query.filter(pi_dsl::created_at.ge(starting_at)), + (None, Some(starting_after_id)) => { + // TODO: Fetch partial columns for this query since we only need some columns + let starting_at = self + .find_payment_intent_by_id( + state, + starting_after_id, + merchant_key_store, + storage_scheme, + ) + .await? + .created_at; + query.filter(pi_dsl::created_at.ge(starting_at)) + } + (None, None) => query, + }; + + query = match (params.ending_at, ¶ms.ending_before_id) { + (Some(ending_at), _) => query.filter(pi_dsl::created_at.le(ending_at)), + (None, Some(ending_before_id)) => { + // TODO: Fetch partial columns for this query since we only need some columns + let ending_at = self + .find_payment_intent_by_id( + state, + ending_before_id, + merchant_key_store, + storage_scheme, + ) + .await? + .created_at; + query.filter(pi_dsl::created_at.le(ending_at)) + } + (None, None) => query, + }; + + query = query.offset(params.offset.into()); + + query = match params.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => query.filter(pi_dsl::amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.le(end)), + _ => query, + }; + + query = match ¶ms.currency { + Some(currency) => query.filter(pi_dsl::currency.eq(*currency)), + None => query, + }; + + query = match ¶ms.connector { + Some(connector) => query.filter(pa_dsl::connector.eq(*connector)), + None => query, + }; + + query = match ¶ms.status { + Some(status) => query.filter(pi_dsl::status.eq(*status)), + None => query, + }; + + query = match ¶ms.payment_method_type { + Some(payment_method_type) => { + query.filter(pa_dsl::payment_method_type_v2.eq(*payment_method_type)) + } + None => query, + }; + + query = match ¶ms.payment_method_subtype { + Some(payment_method_subtype) => { + query.filter(pa_dsl::payment_method_subtype.eq(*payment_method_subtype)) + } + None => query, + }; + + query = match ¶ms.authentication_type { + Some(authentication_type) => { + query.filter(pa_dsl::authentication_type.eq(*authentication_type)) + } + None => query, + }; + + query = match ¶ms.merchant_connector_id { + Some(merchant_connector_id) => query + .filter(pa_dsl::merchant_connector_id.eq(merchant_connector_id.clone())), + None => query, + }; + + if let Some(card_network) = ¶ms.card_network { + query = query.filter(pa_dsl::card_network.eq(card_network.clone())); + } + query + } + }; + + logger::debug!(filter = %diesel::debug_query::(&query).to_string()); + + query + .get_results_async::<( + DieselPaymentIntent, + Option, + )>(conn) + .await + .change_context(StorageError::DecryptionError) + .async_and_then(|output| async { + try_join_all(output.into_iter().map( + |(pi, pa): (_, Option)| async { + let payment_intent = PaymentIntent::convert_back( + state, + pi, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ); + let payment_attempt = pa + .async_map(|val| { + PaymentAttempt::convert_back( + state, + val, + merchant_key_store.key.get_inner(), + merchant_id.to_owned().into(), + ) + }) + .map(|val| val.transpose()); + + let output = futures::try_join!(payment_intent, payment_attempt); + output.change_context(StorageError::DecryptionError) + }, + )) + .await + }) + .await + .change_context(StorageError::DecryptionError) + } + + #[cfg(all(feature = "v2", feature = "olap"))] + #[instrument(skip_all)] + async fn get_filtered_active_attempt_ids_for_total_count( + &self, + merchant_id: &common_utils::id_type::MerchantId, + constraints: &PaymentIntentFetchConstraints, + _storage_scheme: MerchantStorageScheme, + ) -> error_stack::Result>, StorageError> { + let conn = connection::pg_connection_read(self).await.switch()?; + let conn = async_bb8_diesel::Connection::as_async_conn(&conn); + let mut query = DieselPaymentIntent::table() + .select(pi_dsl::active_attempt_id) + .filter(pi_dsl::merchant_id.eq(merchant_id.to_owned())) + .order(pi_dsl::created_at.desc()) + .into_boxed(); + + query = match constraints { + PaymentIntentFetchConstraints::Single { payment_intent_id } => { + query.filter(pi_dsl::id.eq(payment_intent_id.to_owned())) + } + PaymentIntentFetchConstraints::List(params) => { + if let Some(customer_id) = ¶ms.customer_id { + query = query.filter(pi_dsl::customer_id.eq(customer_id.clone())); + } + if let Some(merchant_order_reference_id) = ¶ms.merchant_order_reference_id { + query = query.filter( + pi_dsl::merchant_reference_id.eq(merchant_order_reference_id.clone()), + ) + } + if let Some(profile_id) = ¶ms.profile_id { + query = query.filter(pi_dsl::profile_id.eq(profile_id.clone())); + } + + query = match params.starting_at { + Some(starting_at) => query.filter(pi_dsl::created_at.ge(starting_at)), + None => query, + }; + + query = match params.ending_at { + Some(ending_at) => query.filter(pi_dsl::created_at.le(ending_at)), + None => query, + }; + + query = match params.amount_filter { + Some(AmountFilter { + start_amount: Some(start), + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.between(start, end)), + Some(AmountFilter { + start_amount: Some(start), + end_amount: None, + }) => query.filter(pi_dsl::amount.ge(start)), + Some(AmountFilter { + start_amount: None, + end_amount: Some(end), + }) => query.filter(pi_dsl::amount.le(end)), + _ => query, + }; + + query = match ¶ms.currency { + Some(currency) => query.filter(pi_dsl::currency.eq(*currency)), + None => query, + }; + + query = match ¶ms.status { + Some(status) => query.filter(pi_dsl::status.eq(*status)), + None => query, + }; + + query + } + }; + + db_metrics::track_database_call::<::Table, _, _>( + query.get_results_async::>(conn), + db_metrics::DatabaseOperation::Filter, + ) + .await + .map_err(|er| { + StorageError::DatabaseError( + error_stack::report!(diesel_models::errors::DatabaseError::from(er)) + .attach_printable("Error filtering payment records"), + ) + .into() + }) + } + #[cfg(all(feature = "v1", feature = "olap"))] #[instrument(skip_all)] async fn get_filtered_active_attempt_ids_for_total_count( From e475f63967a7922d1a0211d75ad4567a3182f3e8 Mon Sep 17 00:00:00 2001 From: Prasunna Soppa <70575890+prasunna09@users.noreply.github.com> Date: Thu, 20 Feb 2025 16:49:09 +0530 Subject: [PATCH 120/133] fix(payment_methods_v2): update fingerprint implementation in v2 (#7270) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/router/src/core/payment_methods.rs | 12 ++++++++---- crates/router/src/core/payment_methods/vault.rs | 2 +- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/crates/router/src/core/payment_methods.rs b/crates/router/src/core/payment_methods.rs index 6ebc7bde5e4..237a097b792 100644 --- a/crates/router/src/core/payment_methods.rs +++ b/crates/router/src/core/payment_methods.rs @@ -921,6 +921,7 @@ pub async fn create_payment_method( merchant_account, key_store, None, + &customer_id, ) .await; @@ -1513,14 +1514,16 @@ pub async fn vault_payment_method( merchant_account: &domain::MerchantAccount, key_store: &domain::MerchantKeyStore, existing_vault_id: Option, + customer_id: &id_type::GlobalCustomerId, ) -> RouterResult<(pm_types::AddVaultResponse, String)> { let db = &*state.store; // get fingerprint_id from vault - let fingerprint_id_from_vault = vault::get_fingerprint_id_from_vault(state, pmd) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Failed to get fingerprint_id from vault")?; + let fingerprint_id_from_vault = + vault::get_fingerprint_id_from_vault(state, pmd, customer_id.get_string_repr().to_owned()) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Failed to get fingerprint_id from vault")?; // throw back error if payment method is duplicated when( @@ -1796,6 +1799,7 @@ pub async fn update_payment_method_core( &merchant_account, &key_store, current_vault_id, // using current vault_id for now, will have to refactor this + &payment_method.customer_id, ) // to generate new one on each vaulting later on .await .attach_printable("Failed to add payment method in vault")?; diff --git a/crates/router/src/core/payment_methods/vault.rs b/crates/router/src/core/payment_methods/vault.rs index 300f1e3054f..93461c63152 100644 --- a/crates/router/src/core/payment_methods/vault.rs +++ b/crates/router/src/core/payment_methods/vault.rs @@ -1257,8 +1257,8 @@ pub async fn get_fingerprint_id_from_vault< >( state: &routes::SessionState, data: &D, + key: String, ) -> CustomResult { - let key = data.get_vaulting_data_key(); let data = serde_json::to_string(data) .change_context(errors::VaultError::RequestEncodingFailed) .attach_printable("Failed to encode Vaulting data to string")?; From 451acba005a85228925da5b6c252815d58f96fd7 Mon Sep 17 00:00:00 2001 From: AkshayaFoiger <131388445+AkshayaFoiger@users.noreply.github.com> Date: Thu, 20 Feb 2025 19:29:35 +0530 Subject: [PATCH 121/133] feat(router): [Xendit] add support for split payments (#7143) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- Cargo.lock | 1 + api-reference-v2/openapi_spec.json | 207 +++++++++++++++++ api-reference/openapi_spec.json | 207 +++++++++++++++++ crates/common_types/src/domain.rs | 12 + crates/common_types/src/payments.rs | 97 +++++++- crates/common_types/src/refunds.rs | 4 +- crates/hyperswitch_connectors/Cargo.toml | 1 + .../src/connectors/xendit.rs | 215 ++++++++++++++++- .../src/connectors/xendit/transformers.rs | 218 +++++++++++++++++- .../src/default_implementations.rs | 1 - .../hyperswitch_domain_models/src/payments.rs | 3 + .../src/payments/payment_intent.rs | 4 +- .../src/router_request_types.rs | 6 +- crates/openapi/src/openapi.rs | 6 + crates/openapi/src/openapi_v2.rs | 6 + crates/router/src/connector/stripe.rs | 10 +- .../src/connector/stripe/transformers.rs | 14 +- crates/router/src/core/payments.rs | 11 + .../src/core/payments/flows/authorize_flow.rs | 1 + .../payments/flows/complete_authorize_flow.rs | 1 + crates/router/src/core/payments/helpers.rs | 64 +++++ .../router/src/core/payments/transformers.rs | 4 + crates/router/src/core/refunds/validator.rs | 29 +++ crates/router/src/core/utils.rs | 52 +++++ 24 files changed, 1142 insertions(+), 32 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b4d9767029..b6b54a66a21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4005,6 +4005,7 @@ dependencies = [ "bytes 1.7.1", "cards", "common_enums", + "common_types", "common_utils", "encoding_rs", "error-stack", diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 228aef09dee..2ea34e84690 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -7392,6 +7392,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -20924,6 +20935,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -20951,6 +20973,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -22436,6 +22469,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 587cba45e6a..78760af592a 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9623,6 +9623,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditChargeResponseData" + } + } } ], "description": "Charge Information" @@ -25482,6 +25493,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_payment" + ], + "properties": { + "xendit_split_payment": { + "$ref": "#/components/schemas/XenditSplitRequest" + } + } } ], "description": "Fee information for Split Payments to be charged on the payment being collected" @@ -25509,6 +25531,17 @@ "$ref": "#/components/schemas/AdyenSplitData" } } + }, + { + "type": "object", + "required": [ + "xendit_split_refund" + ], + "properties": { + "xendit_split_refund": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } } ], "description": "Charge specific fields for controlling the revert of funds from either platform or connected account. Check sub-fields for more details." @@ -27054,6 +27087,180 @@ } }, "additionalProperties": false + }, + "XenditChargeResponseData": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitResponse" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Charge Information" + }, + "XenditMultipleSplitRequest": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "name", + "description", + "routes" + ], + "properties": { + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditMultipleSplitResponse": { + "type": "object", + "description": "Fee information charged on the payment being collected via xendit", + "required": [ + "split_rule_id", + "name", + "description", + "routes" + ], + "properties": { + "split_rule_id": { + "type": "string", + "description": "Identifier for split rule created for the payment" + }, + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for.", + "nullable": true + }, + "name": { + "type": "string", + "description": "Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types." + }, + "description": { + "type": "string", + "description": "Description to identify fee rule" + }, + "routes": { + "type": "array", + "items": { + "$ref": "#/components/schemas/XenditSplitRoute" + }, + "description": "Array of objects that define how the platform wants to route the fees and to which accounts." + } + }, + "additionalProperties": false + }, + "XenditSplitRequest": { + "oneOf": [ + { + "type": "object", + "required": [ + "multiple_splits" + ], + "properties": { + "multiple_splits": { + "$ref": "#/components/schemas/XenditMultipleSplitRequest" + } + } + }, + { + "type": "object", + "required": [ + "single_split" + ], + "properties": { + "single_split": { + "$ref": "#/components/schemas/XenditSplitSubMerchantData" + } + } + } + ], + "description": "Xendit Charge Request" + }, + "XenditSplitRoute": { + "type": "object", + "description": "Fee information to be charged on the payment being collected via xendit", + "required": [ + "currency", + "destination_account_id", + "reference_id" + ], + "properties": { + "flat_amount": { + "allOf": [ + { + "$ref": "#/components/schemas/MinorUnit" + } + ], + "nullable": true + }, + "percent_amount": { + "type": "integer", + "format": "int64", + "description": "Amount of payments to be split, using a percent rate as unit", + "nullable": true + }, + "currency": { + "$ref": "#/components/schemas/Currency" + }, + "destination_account_id": { + "type": "string", + "description": "ID of the destination account where the amount will be routed to" + }, + "reference_id": { + "type": "string", + "description": "Reference ID which acts as an identifier of the route itself" + } + }, + "additionalProperties": false + }, + "XenditSplitSubMerchantData": { + "type": "object", + "description": "Fee information to be charged on the payment being collected for sub-merchant via xendit", + "required": [ + "for_user_id" + ], + "properties": { + "for_user_id": { + "type": "string", + "description": "The sub-account user-id that you want to make this transaction for." + } + }, + "additionalProperties": false } }, "securitySchemes": { diff --git a/crates/common_types/src/domain.rs b/crates/common_types/src/domain.rs index 65f6ae9ad68..0361f5b0e46 100644 --- a/crates/common_types/src/domain.rs +++ b/crates/common_types/src/domain.rs @@ -41,3 +41,15 @@ pub struct AdyenSplitItem { pub description: Option, } impl_to_sql_from_sql_json!(AdyenSplitItem); + +/// Fee information to be charged on the payment being collected for sub-merchant via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitSubMerchantData { + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitSubMerchantData); diff --git a/crates/common_types/src/payments.rs b/crates/common_types/src/payments.rs index ba96d618c41..996478e8bfa 100644 --- a/crates/common_types/src/payments.rs +++ b/crates/common_types/src/payments.rs @@ -12,7 +12,7 @@ use euclid::frontend::{ use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -26,6 +26,8 @@ pub enum SplitPaymentsRequest { StripeSplitPayment(StripeSplitPaymentRequest), /// AdyenSplitPayment AdyenSplitPayment(AdyenSplitData), + /// XenditSplitPayment + XenditSplitPayment(XenditSplitRequest), } impl_to_sql_from_sql_json!(SplitPaymentsRequest); @@ -156,6 +158,99 @@ pub enum ConnectorChargeResponseData { StripeSplitPayment(StripeChargeResponseData), /// AdyenChargeResponseData AdyenSplitPayment(AdyenSplitData), + /// XenditChargeResponseData + XenditSplitPayment(XenditChargeResponseData), } impl_to_sql_from_sql_json!(ConnectorChargeResponseData); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditSplitRoute { + /// Amount of payments to be split + pub flat_amount: Option, + /// Amount of payments to be split, using a percent rate as unit + pub percent_amount: Option, + /// Currency code + #[schema(value_type = Currency, example = "USD")] + pub currency: enums::Currency, + /// ID of the destination account where the amount will be routed to + pub destination_account_id: String, + /// Reference ID which acts as an identifier of the route itself + pub reference_id: String, +} +impl_to_sql_from_sql_json!(XenditSplitRoute); + +/// Fee information to be charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitRequest { + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitRequest); + +/// Xendit Charge Request +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditSplitRequest { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitRequest), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditSplitRequest); + +/// Charge Information +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(rename_all = "snake_case")] +#[serde(deny_unknown_fields)] +pub enum XenditChargeResponseData { + /// Split Between Multiple Accounts + MultipleSplits(XenditMultipleSplitResponse), + /// Collect Fee for Single Account + SingleSplit(XenditSplitSubMerchantData), +} + +impl_to_sql_from_sql_json!(XenditChargeResponseData); + +/// Fee information charged on the payment being collected via xendit +#[derive( + Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, +)] +#[diesel(sql_type = Jsonb)] +#[serde(deny_unknown_fields)] +pub struct XenditMultipleSplitResponse { + /// Identifier for split rule created for the payment + pub split_rule_id: String, + /// The sub-account user-id that you want to make this transaction for. + pub for_user_id: Option, + /// Name to identify split rule. Not required to be unique. Typically based on transaction and/or sub-merchant types. + pub name: String, + /// Description to identify fee rule + pub description: String, + /// Array of objects that define how the platform wants to route the fees and to which accounts. + pub routes: Vec, +} +impl_to_sql_from_sql_json!(XenditMultipleSplitResponse); diff --git a/crates/common_types/src/refunds.rs b/crates/common_types/src/refunds.rs index c96937021c8..f7c12f90f84 100644 --- a/crates/common_types/src/refunds.rs +++ b/crates/common_types/src/refunds.rs @@ -5,7 +5,7 @@ use diesel::{sql_types::Jsonb, AsExpression, FromSqlRow}; use serde::{Deserialize, Serialize}; use utoipa::ToSchema; -use crate::domain::AdyenSplitData; +use crate::domain::{AdyenSplitData, XenditSplitSubMerchantData}; #[derive( Serialize, Deserialize, Debug, Clone, PartialEq, Eq, FromSqlRow, AsExpression, ToSchema, @@ -19,6 +19,8 @@ pub enum SplitRefund { StripeSplitRefund(StripeSplitRefundRequest), /// AdyenSplitRefundRequest AdyenSplitRefund(AdyenSplitData), + /// XenditSplitRefundRequest + XenditSplitRefund(XenditSplitSubMerchantData), } impl_to_sql_from_sql_json!(SplitRefund); diff --git a/crates/hyperswitch_connectors/Cargo.toml b/crates/hyperswitch_connectors/Cargo.toml index 13d5266dabd..915b87a9189 100644 --- a/crates/hyperswitch_connectors/Cargo.toml +++ b/crates/hyperswitch_connectors/Cargo.toml @@ -49,6 +49,7 @@ lazy_static = "1.4.0" api_models = { version = "0.1.0", path = "../api_models", features = ["errors"], default-features = false } cards = { version = "0.1.0", path = "../cards" } common_enums = { version = "0.1.0", path = "../common_enums" } +common_types = { version = "0.1.0", path = "../common_types" } common_utils = { version = "0.1.0", path = "../common_utils", features = ["signals", "async_ext", "logs", "metrics"] } hyperswitch_domain_models = { version = "0.1.0", path = "../hyperswitch_domain_models", default-features = false } hyperswitch_interfaces = { version = "0.1.0", path = "../hyperswitch_interfaces", default-features = false } diff --git a/crates/hyperswitch_connectors/src/connectors/xendit.rs b/crates/hyperswitch_connectors/src/connectors/xendit.rs index dae6b8fd88f..c45f637f48a 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit.rs @@ -14,18 +14,22 @@ use hyperswitch_domain_models::{ router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, - payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + payments::{ + Authorize, Capture, PSync, PaymentMethodToken, PreProcessing, Session, SetupMandate, + Void, + }, refunds::{Execute, RSync}, }, router_request_types::{ AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, - PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, - RefundsData, SetupMandateRequestData, + PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSessionData, + PaymentsSyncData, RefundsData, SetupMandateRequestData, SplitRefundsRequest, }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, - PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, + PaymentsPreProcessingRouterData, PaymentsSyncRouterData, RefundSyncRouterData, + RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -61,6 +65,7 @@ impl Xendit { } } impl api::Payment for Xendit {} +impl api::PaymentsPreProcessing for Xendit {} impl api::PaymentSession for Xendit {} impl api::ConnectorAccessToken for Xendit {} impl api::MandateSetup for Xendit {} @@ -121,6 +126,7 @@ impl ConnectorCommon for Xendit { let auth = xendit::XenditAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; let encoded_api_key = BASE64_ENGINE.encode(format!("{}:", auth.api_key.peek())); + Ok(vec![( headers::AUTHORIZATION.to_string(), format!("Basic {encoded_api_key}").into_masked(), @@ -203,7 +209,46 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + if let Ok(PaymentsResponseData::TransactionResponse { + charges: + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits( + xendit_response, + ), + )), + .. + }) = req.response.as_ref() + { + headers.push(( + xendit::auth_headers::WITH_SPLIT_RULE.to_string(), + xendit_response.split_rule_id.clone().into(), + )); + if let Some(for_user_id) = &xendit_response.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -280,13 +325,121 @@ impl ConnectorIntegration + for Xendit +{ + fn get_headers( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/split_rules", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &PaymentsPreProcessingRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = xendit::XenditSplitRequestData::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsPreProcessingRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(types::PaymentsPreProcessingType::get_headers( + self, req, connectors, + )?) + .url(&types::PaymentsPreProcessingType::get_url( + self, req, connectors, + )?) + .set_body(types::PaymentsPreProcessingType::get_request_body( + self, req, connectors, + )?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &PaymentsPreProcessingRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: xendit::XenditSplitResponse = res + .response + .parse_struct("XenditSplitResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(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) + } +} + impl ConnectorIntegration for Xendit { fn get_headers( &self, req: &PaymentsSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -353,7 +506,31 @@ impl ConnectorIntegration fo req: &PaymentsCaptureRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + + match &req.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(xendit_request), + )) => { + if let Some(for_user_id) = &xendit_request.for_user_id { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + for_user_id.clone().into(), + )) + }; + } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(single_split_data), + )) => { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + single_split_data.for_user_id.clone().into(), + )); + } + _ => (), + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -453,7 +630,17 @@ impl ConnectorIntegration for Xendit req: &RefundsRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { @@ -539,7 +726,17 @@ impl ConnectorIntegration for Xendit { req: &RefundSyncRouterData, connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut headers = self.build_headers(req, connectors)?; + if let Some(SplitRefundsRequest::XenditSplitRefund(sub_merchant_data)) = + &req.request.split_refunds + { + headers.push(( + xendit::auth_headers::FOR_USER_ID.to_string(), + sub_merchant_data.for_user_id.clone().into(), + )); + }; + + Ok(headers) } fn get_content_type(&self) -> &'static str { diff --git a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs index 7a7789e73ab..79ac7312350 100644 --- a/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/xendit/transformers.rs @@ -7,13 +7,15 @@ use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, router_data::{ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::refunds::{Execute, RSync}, - router_request_types::{PaymentsAuthorizeData, PaymentsCaptureData, ResponseId}, + router_request_types::{ + PaymentsAuthorizeData, PaymentsCaptureData, PaymentsPreProcessingData, ResponseId, + }, router_response_types::{ MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, }, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsPreProcessingRouterData, + PaymentsSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -89,6 +91,38 @@ pub struct XenditPaymentsRequest { pub channel_properties: Option, } +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRoute { + #[serde(skip_serializing_if = "Option::is_none")] + pub flat_amount: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub percent_amount: Option, + pub currency: enums::Currency, + pub destination_account_id: String, + pub reference_id: String, +} + +#[derive(Serialize, Deserialize, Debug, Clone)] +pub struct XenditSplitRequest { + pub name: String, + pub description: String, + pub routes: Vec, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitRequestData { + #[serde(flatten)] + pub split_data: XenditSplitRequest, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct XenditSplitResponse { + id: String, + name: String, + description: String, + routes: Vec, +} + #[derive(Serialize, Deserialize, Debug)] pub struct CardInfo { pub channel_properties: ChannelProperties, @@ -104,6 +138,11 @@ pub struct CardInformation { pub cardholder_email: pii::Email, pub cardholder_phone_number: Secret, } +pub mod auth_headers { + pub const WITH_SPLIT_RULE: &str = "with-split-rule"; + pub const FOR_USER_ID: &str = "for-user-id"; +} + #[derive(Serialize, Deserialize, Debug)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum TransactionType { @@ -288,6 +327,35 @@ impl status_code: item.http_code, }) } else { + let charges = match item.data.request.split_payments.as_ref() { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => item + .data + .response + .as_ref() + .ok() + .and_then(|response| match response { + PaymentsResponseData::TransactionResponse { charges, .. } => { + charges.clone() + } + _ => None, + }), + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::SingleSplit(ref split_data), + )) => { + let charges = common_types::domain::XenditSplitSubMerchantData { + for_user_id: split_data.for_user_id.clone(), + }; + Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::SingleSplit(charges), + ), + ) + } + _ => None, + }; + Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.id.clone()), redirection_data: match item.response.actions { @@ -320,7 +388,7 @@ impl item.response.reference_id.peek().to_string(), ), incremental_authorization_allowed: None, - charges: None, + charges, }) }; Ok(Self { @@ -388,6 +456,90 @@ impl }) } } + +impl + TryFrom< + ResponseRouterData, + > for RouterData +{ + type Error = error_stack::Report; + + fn try_from( + item: ResponseRouterData< + F, + XenditSplitResponse, + PaymentsPreProcessingData, + PaymentsResponseData, + >, + ) -> Result { + let for_user_id = match item.data.request.split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) => split_data.for_user_id.clone(), + _ => None, + }; + + let routes: Vec = item + .response + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert_back( + &required_conversion_type, + amount, + item.data.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| common_types::payments::XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let charges = common_types::payments::XenditMultipleSplitResponse { + split_rule_id: item.response.id, + for_user_id, + name: item.response.name, + description: item.response.description, + routes, + }; + + let response = PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::NoResponseId, + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: Some( + common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + common_types::payments::XenditChargeResponseData::MultipleSplits(charges), + ), + ), + }; + + Ok(Self { + response: Ok(response), + ..item.data + }) + } +} + impl TryFrom> for PaymentsSyncRouterData { type Error = error_stack::Report; fn try_from( @@ -461,6 +613,59 @@ impl TryFrom<&ConnectorAuthType> for XenditAuthType { } } +impl TryFrom<&PaymentsPreProcessingRouterData> for XenditSplitRequestData { + type Error = error_stack::Report; + fn try_from(item: &PaymentsPreProcessingRouterData) -> Result { + if let Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(ref split_data), + )) = item.request.split_payments.clone() + { + let routes: Vec = split_data + .routes + .iter() + .map(|route| { + let required_conversion_type = common_utils::types::FloatMajorUnitForConnector; + route + .flat_amount + .map(|amount| { + common_utils::types::AmountConvertor::convert( + &required_conversion_type, + amount, + item.request.currency.unwrap_or(enums::Currency::USD), + ) + .map_err(|_| { + errors::ConnectorError::RequestEncodingFailedWithReason( + "Failed to convert the amount into a major unit".to_owned(), + ) + }) + }) + .transpose() + .map(|flat_amount| XenditSplitRoute { + flat_amount, + percent_amount: route.percent_amount, + currency: route.currency, + destination_account_id: route.destination_account_id.clone(), + reference_id: route.reference_id.clone(), + }) + }) + .collect::, _>>()?; + + let split_data = XenditSplitRequest { + name: split_data.name.clone(), + description: split_data.description.clone(), + routes, + }; + + Ok(Self { split_data }) + } else { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("Xendit"), + ) + .into()) + } + } +} + //TODO: Fill the struct with respective fields // REFUND : // Type definition for RefundRequest @@ -538,3 +743,8 @@ impl TryFrom> for RefundsRouter }) } } + +#[derive(Default, Debug, Serialize, Deserialize)] +pub struct XenditMetadata { + pub for_user_id: String, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index cd78cb47f9d..b2df2d15f71 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -816,7 +816,6 @@ default_imp_for_pre_processing_steps!( connectors::Worldpay, connectors::Wellsfargo, connectors::Volt, - connectors::Xendit, connectors::Zen, connectors::Zsl, connectors::CtpMastercard diff --git a/crates/hyperswitch_domain_models/src/payments.rs b/crates/hyperswitch_domain_models/src/payments.rs index 0b98885fdaf..606a8af51c3 100644 --- a/crates/hyperswitch_domain_models/src/payments.rs +++ b/crates/hyperswitch_domain_models/src/payments.rs @@ -369,6 +369,8 @@ pub struct PaymentIntent { pub routing_algorithm_id: Option, /// Identifier for the platform merchant. pub platform_merchant_id: Option, + /// Split Payment Data + pub split_payments: Option, } #[cfg(feature = "v2")] @@ -511,6 +513,7 @@ impl PaymentIntent { routing_algorithm_id: request.routing_algorithm_id, platform_merchant_id: platform_merchant_id .map(|merchant_account| merchant_account.get_id().to_owned()), + split_payments: None, }) } } diff --git a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs index 9d4debacaba..cc1a7054ded 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_intent.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_intent.rs @@ -1475,6 +1475,7 @@ impl behaviour::Conversion for PaymentIntent { routing_algorithm_id, payment_link_config, platform_merchant_id, + split_payments, } = self; Ok(DieselPaymentIntent { skip_external_tax_calculation: Some(amount_details.get_external_tax_action_as_bool()), @@ -1547,7 +1548,7 @@ impl behaviour::Conversion for PaymentIntent { psd2_sca_exemption_type: None, request_extended_authorization: None, platform_merchant_id, - split_payments: None, + split_payments, }) } async fn convert_back( @@ -1674,6 +1675,7 @@ impl behaviour::Conversion for PaymentIntent { payment_link_config: storage_model.payment_link_config, routing_algorithm_id: storage_model.routing_algorithm_id, platform_merchant_id: storage_model.platform_merchant_id, + split_payments: storage_model.split_payments, }) } .await diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 90f97816091..071a77aa7bd 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -118,7 +118,7 @@ pub struct PaymentsCaptureData { pub metadata: Option, // This metadata is used to store the metadata shared during the payment intent request. pub capture_method: Option, - + pub split_payments: Option, // New amount for amount frame work pub minor_payment_amount: MinorUnit, pub minor_amount_to_capture: MinorUnit, @@ -291,6 +291,7 @@ pub struct PaymentsPreProcessingData { pub related_transaction_id: Option, pub redirect_response: Option, pub metadata: Option>, + pub split_payments: Option, // New amount for amount frame work pub minor_amount: Option, @@ -320,6 +321,7 @@ impl TryFrom for PaymentsPreProcessingData { related_transaction_id: data.related_transaction_id, redirect_response: None, enrolled_for_3ds: data.enrolled_for_3ds, + split_payments: data.split_payments, metadata: data.metadata.map(Secret::new), }) } @@ -348,6 +350,7 @@ impl TryFrom for PaymentsPreProcessingData { mandate_id: data.mandate_id, related_transaction_id: None, redirect_response: data.redirect_response, + split_payments: None, enrolled_for_3ds: true, metadata: data.connector_meta.map(Secret::new), }) @@ -648,6 +651,7 @@ pub struct RefundIntegrityObject { pub enum SplitRefundsRequest { StripeSplitRefund(StripeSplitRefund), AdyenSplitRefund(common_types::domain::AdyenSplitData), + XenditSplitRefund(common_types::domain::XenditSplitSubMerchantData), } #[derive(Debug, serde::Deserialize, Clone)] diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index eb5edb08fab..b75d1d22854 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -220,6 +220,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, common_types::domain::AdyenSplitItem, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_utils::types::ChargeRefunds, common_types::refunds::SplitRefund, common_types::refunds::StripeSplitRefundRequest, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 12e42320b40..752bd9707ff 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -165,6 +165,12 @@ Never share your secret api keys. Keep them guarded and secure. common_types::payments::SplitPaymentsRequest, common_types::payments::StripeSplitPaymentRequest, common_types::domain::AdyenSplitData, + common_types::payments::XenditSplitRequest, + common_types::payments::XenditSplitRoute, + common_types::payments::XenditChargeResponseData, + common_types::payments::XenditMultipleSplitResponse, + common_types::payments::XenditMultipleSplitRequest, + common_types::domain::XenditSplitSubMerchantData, common_types::domain::AdyenSplitItem, common_types::refunds::StripeSplitRefundRequest, common_utils::types::ChargeRefunds, diff --git a/crates/router/src/connector/stripe.rs b/crates/router/src/connector/stripe.rs index 66e20e54616..a971bd96741 100644 --- a/crates/router/src/connector/stripe.rs +++ b/crates/router/src/connector/stripe.rs @@ -1520,15 +1520,13 @@ impl services::ConnectorIntegration { - RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( - req, - refund_amount, - ))?)) - } Some(SplitRefundsRequest::StripeSplitRefund(_)) => RequestContent::FormUrlEncoded( Box::new(stripe::ChargeRefundRequest::try_from(req)?), ), + _ => RequestContent::FormUrlEncoded(Box::new(stripe::RefundRequest::try_from(( + req, + refund_amount, + ))?)), }; Ok(request_body) } diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 7611f375c09..cdcf7b4c7b2 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1973,9 +1973,9 @@ impl TryFrom<(&types::PaymentsAuthorizeRouterData, MinorUnit)> for PaymentIntent }; (charges, None) } - Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) | None => { - (None, item.connector_customer.to_owned().map(Secret::new)) - } + Some(common_types::payments::SplitPaymentsRequest::AdyenSplitPayment(_)) + | Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) + | None => (None, item.connector_customer.to_owned().map(Secret::new)), }; Ok(Self { @@ -3092,11 +3092,9 @@ impl TryFrom<&types::RefundsRouterData> for ChargeRefundRequest { }, }) } - types::SplitRefundsRequest::AdyenSplitRefund(_) => { - Err(errors::ConnectorError::MissingRequiredField { - field_name: "stripe_split_refund", - })? - } + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "stripe_split_refund", + })?, }, } } diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index d97a52ab55b..74ac9dc0f37 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -4202,6 +4202,17 @@ where { router_data = router_data.preprocessing_steps(state, connector).await?; (router_data, should_continue_payment) + } else if connector.connector_name == router_types::Connector::Xendit { + match payment_data.get_payment_intent().split_payments { + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + common_types::payments::XenditSplitRequest::MultipleSplits(_), + )) => { + router_data = router_data.preprocessing_steps(state, connector).await?; + let is_error_in_response = router_data.response.is_err(); + (router_data, !is_error_in_response) + } + _ => (router_data, should_continue_payment), + } } else { (router_data, should_continue_payment) } diff --git a/crates/router/src/core/payments/flows/authorize_flow.rs b/crates/router/src/core/payments/flows/authorize_flow.rs index 7efa9a5e180..b5104391ab8 100644 --- a/crates/router/src/core/payments/flows/authorize_flow.rs +++ b/crates/router/src/core/payments/flows/authorize_flow.rs @@ -626,6 +626,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: item.request.split_payments, }) } } diff --git a/crates/router/src/core/payments/flows/complete_authorize_flow.rs b/crates/router/src/core/payments/flows/complete_authorize_flow.rs index 44bb9f2703a..6e8152716d0 100644 --- a/crates/router/src/core/payments/flows/complete_authorize_flow.rs +++ b/crates/router/src/core/payments/flows/complete_authorize_flow.rs @@ -318,6 +318,7 @@ impl minor_payment_amount: item.request.minor_amount, minor_amount_to_capture: item.request.minor_amount, integrity_object: None, + split_payments: None, }) } } diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 4bfc0db5537..1803e12f0b2 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6993,6 +6993,70 @@ pub fn validate_platform_request_for_marketplace( Ok(()) })?; } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment( + xendit_split_payment, + )) => match xendit_split_payment { + common_types::payments::XenditSplitRequest::MultipleSplits( + xendit_multiple_split_payment, + ) => { + match amount { + api::Amount::Zero => { + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .iter() + .map(|route| { + route + .flat_amount + .unwrap_or(MinorUnit::new(0)) + .get_amount_as_i64() + }) + .sum(); + + if total_split_amount != 0 { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: + "Sum of split amounts should be equal to the total amount", + }); + } + } + api::Amount::Value(amount) => { + let total_payment_amount: i64 = amount.into(); + let total_split_amount: i64 = xendit_multiple_split_payment + .routes + .into_iter() + .map(|route| { + if route.flat_amount.is_none() && route.percent_amount.is_none() { + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount to be provided".to_string(), + }) + } else if route.flat_amount.is_some() && route.percent_amount.is_some(){ + Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Expected either split_payments.xendit_split_payment.routes.flat_amount or split_payments.xendit_split_payment.routes.percent_amount, but not both".to_string(), + }) + } else { + Ok(route + .flat_amount + .map(|amount| amount.get_amount_as_i64()) + .or(route.percent_amount.map(|percentage| (percentage * total_payment_amount) / 100)) + .unwrap_or(0)) + } + }) + .collect::, _>>()? + .into_iter() + .sum(); + + if total_payment_amount < total_split_amount { + return Err(errors::ApiErrorResponse::PreconditionFailed { + message: + "The sum of split amounts should not exceed the total amount" + .to_string(), + }); + } + } + }; + } + common_types::payments::XenditSplitRequest::SingleSplit(_) => (), + }, None => (), } Ok(()) diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 630f6176726..5c77452b677 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -474,6 +474,7 @@ pub async fn construct_payment_router_data_for_capture<'a>( browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }; // TODO: evaluate the fields in router data, if they are required or not @@ -3341,6 +3342,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info: None, metadata: payment_data.payment_intent.metadata.expose_option(), integrity_object: None, + split_payments: None, }) } } @@ -3396,6 +3398,7 @@ impl TryFrom> for types::PaymentsCaptureD browser_info, metadata: payment_data.payment_intent.metadata, integrity_object: None, + split_payments: payment_data.payment_intent.split_payments, }) } } @@ -4114,6 +4117,7 @@ impl TryFrom> for types::PaymentsPreProce mandate_id: payment_data.mandate_id, related_transaction_id: None, enrolled_for_3ds: true, + split_payments: payment_data.payment_intent.split_payments, metadata: payment_data.payment_intent.metadata.map(Secret::new), }) } diff --git a/crates/router/src/core/refunds/validator.rs b/crates/router/src/core/refunds/validator.rs index 90c4a7c4434..573543dfbf7 100644 --- a/crates/router/src/core/refunds/validator.rs +++ b/crates/router/src/core/refunds/validator.rs @@ -252,3 +252,32 @@ pub fn validate_adyen_charge_refund( } Ok(()) } +pub fn validate_xendit_charge_refund( + xendit_split_payment_response: &common_types::payments::XenditChargeResponseData, + xendit_split_refund_request: &common_types::domain::XenditSplitSubMerchantData, +) -> RouterResult> { + match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id + != Some(xendit_split_refund_request.for_user_id.clone()) + { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + common_types::payments::XenditChargeResponseData::SingleSplit( + payment_sub_merchant_data, + ) => { + if payment_sub_merchant_data.for_user_id != xendit_split_refund_request.for_user_id { + return Err(errors::ApiErrorResponse::InvalidDataValue { + field_name: "xendit_split_refund.for_user_id does not match xendit_split_payment.for_user_id", + }.into()); + } + Ok(Some(xendit_split_refund_request.for_user_id.clone())) + } + } +} diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 49075e7f548..8f5a80b24db 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -538,6 +538,58 @@ pub fn get_split_refunds( _ => Ok(None), } } + Some(common_types::payments::SplitPaymentsRequest::XenditSplitPayment(_)) => { + match ( + &split_refund_input.payment_charges, + &split_refund_input.refund_request, + ) { + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + Some(common_types::refunds::SplitRefund::XenditSplitRefund( + split_refund_request, + )), + ) => { + let user_id = super::refunds::validator::validate_xendit_charge_refund( + xendit_split_payment_response, + split_refund_request, + )?; + + Ok(user_id.map(|for_user_id| { + router_request_types::SplitRefundsRequest::XenditSplitRefund( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) + })) + } + ( + Some(common_types::payments::ConnectorChargeResponseData::XenditSplitPayment( + xendit_split_payment_response, + )), + None, + ) => { + let option_for_user_id = match xendit_split_payment_response { + common_types::payments::XenditChargeResponseData::MultipleSplits( + common_types::payments::XenditMultipleSplitResponse { + for_user_id, .. + }, + ) => for_user_id.clone(), + common_types::payments::XenditChargeResponseData::SingleSplit( + common_types::domain::XenditSplitSubMerchantData { for_user_id }, + ) => Some(for_user_id.clone()), + }; + + if option_for_user_id.is_some() { + Err(errors::ApiErrorResponse::MissingRequiredField { + field_name: "split_refunds.xendit_split_refund.for_user_id", + })? + } else { + Ok(None) + } + } + _ => Ok(None), + } + } _ => Ok(None), } } From 2b74a94e3abf6f493058b4a4981f45be558ea3d2 Mon Sep 17 00:00:00 2001 From: DEEPANSHU BANSAL <41580413+deepanshu-iiitu@users.noreply.github.com> Date: Thu, 20 Feb 2025 22:09:28 +0530 Subject: [PATCH 122/133] fix(connector): [DATATRANS] Add new payment status (#7327) --- .../src/connectors/datatrans/transformers.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index e7760c205b6..68daa3743bf 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -93,6 +93,8 @@ pub enum TransactionStatus { Canceled, Transmitted, Failed, + ChallengeOngoing, + ChallengeRequired, } #[derive(Debug, Deserialize, Clone, Serialize)] @@ -499,6 +501,9 @@ impl From for enums::AttemptStatus { TransactionType::Payment => match item.status { TransactionStatus::Authorized => Self::Authorized, TransactionStatus::Settled | TransactionStatus::Transmitted => Self::Charged, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::AuthenticationPending + } TransactionStatus::Canceled => Self::Voided, TransactionStatus::Failed => Self::Failure, TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, @@ -507,6 +512,9 @@ impl From for enums::AttemptStatus { TransactionStatus::Settled | TransactionStatus::Transmitted | TransactionStatus::Authorized => Self::Charged, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::AuthenticationPending + } TransactionStatus::Canceled => Self::Voided, TransactionStatus::Failed => Self::Failure, TransactionStatus::Initialized | TransactionStatus::Authenticated => Self::Pending, @@ -521,6 +529,9 @@ impl From for enums::RefundStatus { match item.res_type { TransactionType::Credit => match item.status { TransactionStatus::Settled | TransactionStatus::Transmitted => Self::Success, + TransactionStatus::ChallengeOngoing | TransactionStatus::ChallengeRequired => { + Self::Pending + } TransactionStatus::Initialized | TransactionStatus::Authenticated | TransactionStatus::Authorized From de865bd13440f965c0d3ffbe44a71925978212dd Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 21 Feb 2025 00:27:53 +0000 Subject: [PATCH 123/133] chore(version): 2025.02.21.0 --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d7fbf16041..f85a7d2ff42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,24 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.21.0 + +### Features + +- **core:** Add support for confirmation flow for click to pay ([#6982](https://github.com/juspay/hyperswitch/pull/6982)) ([`74bbf4b`](https://github.com/juspay/hyperswitch/commit/74bbf4bf271d45e61e594857707250c95a86f43f)) +- **router:** + - Add Payments - List endpoint for v2 ([#7191](https://github.com/juspay/hyperswitch/pull/7191)) ([`d1f537e`](https://github.com/juspay/hyperswitch/commit/d1f537e22909a3580dbf9069852f4257fdbad66b)) + - [Xendit] add support for split payments ([#7143](https://github.com/juspay/hyperswitch/pull/7143)) ([`451acba`](https://github.com/juspay/hyperswitch/commit/451acba005a85228925da5b6c252815d58f96fd7)) + +### Bug Fixes + +- **connector:** [DATATRANS] Add new payment status ([#7327](https://github.com/juspay/hyperswitch/pull/7327)) ([`2b74a94`](https://github.com/juspay/hyperswitch/commit/2b74a94e3abf6f493058b4a4981f45be558ea3d2)) +- **payment_methods_v2:** Update fingerprint implementation in v2 ([#7270](https://github.com/juspay/hyperswitch/pull/7270)) ([`e475f63`](https://github.com/juspay/hyperswitch/commit/e475f63967a7922d1a0211d75ad4567a3182f3e8)) + +**Full Changelog:** [`2025.02.20.0...2025.02.21.0`](https://github.com/juspay/hyperswitch/compare/2025.02.20.0...2025.02.21.0) + +- - - + ## 2025.02.20.0 ### Features From 0e96e2470c6906fe77b35c14821e3ebcfa454e39 Mon Sep 17 00:00:00 2001 From: awasthi21 <107559116+awasthi21@users.noreply.github.com> Date: Fri, 21 Feb 2025 13:43:14 +0530 Subject: [PATCH 124/133] fix(connector): [DATATRANS] Fix Force Sync Flow (#7331) --- .../src/connectors/datatrans/transformers.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs index 68daa3743bf..9297f783d3e 100644 --- a/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/datatrans/transformers.rs @@ -129,7 +129,7 @@ pub struct SyncResponse { #[derive(Serialize, Deserialize, Clone, Debug)] #[serde(rename_all = "camelCase")] pub struct SyncCardDetails { - pub alias: String, + pub alias: Option, } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -787,9 +787,12 @@ impl TryFrom> connector_transaction_id: None, }) } else { - let mandate_reference = - sync_response.card.as_ref().map(|card| MandateReference { - connector_mandate_id: Some(card.alias.clone()), + let mandate_reference = sync_response + .card + .as_ref() + .and_then(|card| card.alias.as_ref()) + .map(|alias| MandateReference { + connector_mandate_id: Some(alias.clone()), payment_method_id: None, mandate_metadata: None, connector_mandate_request_reference_id: None, From 049fcdb3fb19c6af45392a5ef3a0cbf642598af8 Mon Sep 17 00:00:00 2001 From: Sarthak Soni <76486416+Sarthak1799@users.noreply.github.com> Date: Fri, 21 Feb 2025 23:14:11 +0530 Subject: [PATCH 125/133] fix(routing): Fixed 5xx error logs in dynamic routing metrics (#7335) --- crates/router/src/core/routing/helpers.rs | 739 +++++++++++----------- 1 file changed, 378 insertions(+), 361 deletions(-) diff --git a/crates/router/src/core/routing/helpers.rs b/crates/router/src/core/routing/helpers.rs index 6a21dddc066..b8a7ee5dc1c 100644 --- a/crates/router/src/core/routing/helpers.rs +++ b/crates/router/src/core/routing/helpers.rs @@ -727,225 +727,231 @@ pub async fn push_metrics_with_update_window_for_success_based_routing( dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { - let success_based_algo_ref = dynamic_routing_algo_ref - .success_based_algorithm - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("success_based_algorithm not found in dynamic_routing_algorithm from business_profile table")?; - - if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { - let client = state - .grpc_client - .dynamic_routing - .success_rate_client - .as_ref() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "success_rate gRPC client not found".to_string(), - })?; - - let payment_connector = &payment_attempt.connector.clone().ok_or( - errors::ApiErrorResponse::GenericNotFoundError { - message: "unable to derive payment connector from payment attempt".to_string(), - }, - )?; - - let success_based_routing_configs = - fetch_dynamic_routing_configs::( + if let Some(success_based_algo_ref) = dynamic_routing_algo_ref.success_based_algorithm { + if success_based_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { + let client = state + .grpc_client + .dynamic_routing + .success_rate_client + .as_ref() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate gRPC client not found".to_string(), + })?; + + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; + + let success_based_routing_configs = fetch_dynamic_routing_configs::< + routing_types::SuccessBasedRoutingConfig, + >( state, profile_id, success_based_algo_ref .algorithm_id_with_timestamp .algorithm_id - .ok_or(errors::ApiErrorResponse::InternalServerError) + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate algorithm_id not found".to_string(), + }) .attach_printable( "success_based_routing_algorithm_id not found in business_profile", )?, ) .await - .change_context(errors::ApiErrorResponse::InternalServerError) + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "success_rate based dynamic routing configs not found".to_string(), + }) .attach_printable("unable to retrieve success_rate based dynamic routing configs")?; - let success_based_routing_config_params = dynamic_routing_config_params_interpolator - .get_string_val( - success_based_routing_configs - .params - .as_ref() - .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError) - .change_context(errors::ApiErrorResponse::InternalServerError)?, - ); + let success_based_routing_config_params = dynamic_routing_config_params_interpolator + .get_string_val( + success_based_routing_configs + .params + .as_ref() + .ok_or(errors::RoutingError::SuccessBasedRoutingParamsNotFoundError) + .change_context(errors::ApiErrorResponse::InternalServerError)?, + ); - let success_based_connectors = client - .calculate_entity_and_global_success_rate( - profile_id.get_string_repr().into(), - success_based_routing_configs.clone(), - success_based_routing_config_params.clone(), - routable_connectors.clone(), - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to calculate/fetch success rate from dynamic routing service", - )?; + let success_based_connectors = client + .calculate_entity_and_global_success_rate( + profile_id.get_string_repr().into(), + success_based_routing_configs.clone(), + success_based_routing_config_params.clone(), + routable_connectors.clone(), + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to calculate/fetch success rate from dynamic routing service", + )?; - let payment_status_attribute = - get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + let payment_status_attribute = + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); - let first_merchant_success_based_connector = &success_based_connectors - .entity_scores_with_labels - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first connector from list of connectors obtained from dynamic routing service", - )?; + let first_merchant_success_based_connector = &success_based_connectors + .entity_scores_with_labels + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first connector from list of connectors obtained from dynamic routing service", + )?; - let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label - .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!( - "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", - first_merchant_success_based_connector.label - ))?; - - let first_global_success_based_connector = &success_based_connectors - .global_scores_with_labels - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", - )?; + let (first_merchant_success_based_connector_label, _) = first_merchant_success_based_connector.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", + first_merchant_success_based_connector.label + ))?; + + let first_global_success_based_connector = &success_based_connectors + .global_scores_with_labels + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first global connector from list of connectors obtained from dynamic routing service", + )?; - let outcome = get_dynamic_routing_based_metrics_outcome_for_payment( - payment_status_attribute, - payment_connector.to_string(), - first_merchant_success_based_connector_label.to_string(), - ); - - let dynamic_routing_stats = DynamicRoutingStatsNew { - payment_id: payment_attempt.payment_id.to_owned(), - attempt_id: payment_attempt.attempt_id.clone(), - merchant_id: payment_attempt.merchant_id.to_owned(), - profile_id: payment_attempt.profile_id.to_owned(), - amount: payment_attempt.get_total_amount(), - success_based_routing_connector: first_merchant_success_based_connector_label - .to_string(), - payment_connector: payment_connector.to_string(), - payment_method_type: payment_attempt.payment_method_type, - currency: payment_attempt.currency, - payment_method: payment_attempt.payment_method, - capture_method: payment_attempt.capture_method, - authentication_type: payment_attempt.authentication_type, - payment_status: payment_attempt.status, - conclusive_classification: outcome, - created_at: common_utils::date_time::now(), - global_success_based_connector: Some( - first_global_success_based_connector.label.to_string(), - ), - }; + let outcome = get_dynamic_routing_based_metrics_outcome_for_payment( + payment_status_attribute, + payment_connector.to_string(), + first_merchant_success_based_connector_label.to_string(), + ); - core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( - 1, - router_env::metric_attributes!( - ( - "tenant", - state.tenant.tenant_id.get_string_repr().to_owned(), - ), - ( - "merchant_profile_id", - format!( - "{}:{}", - payment_attempt.merchant_id.get_string_repr(), - payment_attempt.profile_id.get_string_repr() - ), - ), - ( - "merchant_specific_success_based_routing_connector", - first_merchant_success_based_connector_label.to_string(), - ), - ( - "merchant_specific_success_based_routing_connector_score", - first_merchant_success_based_connector.score.to_string(), - ), - ( - "global_success_based_routing_connector", + let dynamic_routing_stats = DynamicRoutingStatsNew { + payment_id: payment_attempt.payment_id.to_owned(), + attempt_id: payment_attempt.attempt_id.clone(), + merchant_id: payment_attempt.merchant_id.to_owned(), + profile_id: payment_attempt.profile_id.to_owned(), + amount: payment_attempt.get_total_amount(), + success_based_routing_connector: first_merchant_success_based_connector_label + .to_string(), + payment_connector: payment_connector.to_string(), + payment_method_type: payment_attempt.payment_method_type, + currency: payment_attempt.currency, + payment_method: payment_attempt.payment_method, + capture_method: payment_attempt.capture_method, + authentication_type: payment_attempt.authentication_type, + payment_status: payment_attempt.status, + conclusive_classification: outcome, + created_at: common_utils::date_time::now(), + global_success_based_connector: Some( first_global_success_based_connector.label.to_string(), ), - ( - "global_success_based_routing_connector_score", - first_global_success_based_connector.score.to_string(), - ), - ("payment_connector", payment_connector.to_string()), - ( - "currency", - payment_attempt - .currency - .map_or_else(|| "None".to_string(), |currency| currency.to_string()), - ), - ( - "payment_method", - payment_attempt.payment_method.map_or_else( - || "None".to_string(), - |payment_method| payment_method.to_string(), + }; + + core_metrics::DYNAMIC_SUCCESS_BASED_ROUTING.add( + 1, + router_env::metric_attributes!( + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), ), - ), - ( - "payment_method_type", - payment_attempt.payment_method_type.map_or_else( - || "None".to_string(), - |payment_method_type| payment_method_type.to_string(), + ( + "merchant_profile_id", + format!( + "{}:{}", + payment_attempt.merchant_id.get_string_repr(), + payment_attempt.profile_id.get_string_repr() + ), ), - ), - ( - "capture_method", - payment_attempt.capture_method.map_or_else( - || "None".to_string(), - |capture_method| capture_method.to_string(), + ( + "merchant_specific_success_based_routing_connector", + first_merchant_success_based_connector_label.to_string(), ), - ), - ( - "authentication_type", - payment_attempt.authentication_type.map_or_else( - || "None".to_string(), - |authentication_type| authentication_type.to_string(), + ( + "merchant_specific_success_based_routing_connector_score", + first_merchant_success_based_connector.score.to_string(), + ), + ( + "global_success_based_routing_connector", + first_global_success_based_connector.label.to_string(), + ), + ( + "global_success_based_routing_connector_score", + first_global_success_based_connector.score.to_string(), + ), + ("payment_connector", payment_connector.to_string()), + ( + "currency", + payment_attempt + .currency + .map_or_else(|| "None".to_string(), |currency| currency.to_string()), + ), + ( + "payment_method", + payment_attempt.payment_method.map_or_else( + || "None".to_string(), + |payment_method| payment_method.to_string(), + ), + ), + ( + "payment_method_type", + payment_attempt.payment_method_type.map_or_else( + || "None".to_string(), + |payment_method_type| payment_method_type.to_string(), + ), + ), + ( + "capture_method", + payment_attempt.capture_method.map_or_else( + || "None".to_string(), + |capture_method| capture_method.to_string(), + ), ), + ( + "authentication_type", + payment_attempt.authentication_type.map_or_else( + || "None".to_string(), + |authentication_type| authentication_type.to_string(), + ), + ), + ("payment_status", payment_attempt.status.to_string()), + ("conclusive_classification", outcome.to_string()), ), - ("payment_status", payment_attempt.status.to_string()), - ("conclusive_classification", outcome.to_string()), - ), - ); - logger::debug!("successfully pushed success_based_routing metrics"); - - state - .store - .insert_dynamic_routing_stat_entry(dynamic_routing_stats) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("Unable to push dynamic routing stats to db")?; - - client - .update_success_rate( - profile_id.get_string_repr().into(), - success_based_routing_configs, - success_based_routing_config_params, - vec![routing_types::RoutableConnectorChoiceWithStatus::new( - routing_types::RoutableConnectorChoice { - choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, - connector: common_enums::RoutableConnectors::from_str( - payment_connector.as_str(), - ) - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to infer routable_connector from connector")?, - merchant_connector_id: payment_attempt.merchant_connector_id.clone(), - }, - payment_status_attribute == common_enums::AttemptStatus::Charged, - )], - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to update success based routing window in dynamic routing service", - )?; - Ok(()) + ); + logger::debug!("successfully pushed success_based_routing metrics"); + + state + .store + .insert_dynamic_routing_stat_entry(dynamic_routing_stats) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable("Unable to push dynamic routing stats to db")?; + + client + .update_success_rate( + profile_id.get_string_repr().into(), + success_based_routing_configs, + success_based_routing_config_params, + vec![routing_types::RoutableConnectorChoiceWithStatus::new( + routing_types::RoutableConnectorChoice { + choice_kind: api_models::routing::RoutableChoiceKind::FullStruct, + connector: common_enums::RoutableConnectors::from_str( + payment_connector.as_str(), + ) + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to infer routable_connector from connector", + )?, + merchant_connector_id: payment_attempt.merchant_connector_id.clone(), + }, + payment_status_attribute == common_enums::AttemptStatus::Charged, + )], + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update success based routing window in dynamic routing service", + )?; + Ok(()) + } else { + Ok(()) + } } else { Ok(()) } @@ -962,194 +968,205 @@ pub async fn push_metrics_with_update_window_for_contract_based_routing( dynamic_routing_algo_ref: routing_types::DynamicRoutingAlgorithmRef, _dynamic_routing_config_params_interpolator: DynamicRoutingConfigParamsInterpolator, ) -> RouterResult<()> { - let contract_routing_algo_ref = dynamic_routing_algo_ref - .contract_based_routing - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("contract_routing_algorithm not found in dynamic_routing_algorithm from business_profile table")?; - - if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None { - let client = state - .grpc_client - .dynamic_routing - .contract_based_client - .clone() - .ok_or(errors::ApiErrorResponse::GenericNotFoundError { - message: "contract_routing gRPC client not found".to_string(), - })?; - - let payment_connector = &payment_attempt.connector.clone().ok_or( - errors::ApiErrorResponse::GenericNotFoundError { - message: "unable to derive payment connector from payment attempt".to_string(), - }, - )?; - - let contract_based_routing_config = - fetch_dynamic_routing_configs::( - state, - profile_id, - contract_routing_algo_ref - .algorithm_id_with_timestamp - .algorithm_id - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "contract_based_routing_algorithm_id not found in business_profile", - )?, - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to retrieve contract based dynamic routing configs")?; - - let mut existing_label_info = None; + if let Some(contract_routing_algo_ref) = dynamic_routing_algo_ref.contract_based_routing { + if contract_routing_algo_ref.enabled_feature != routing_types::DynamicRoutingFeatures::None + { + let client = state + .grpc_client + .dynamic_routing + .contract_based_client + .clone() + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract_routing gRPC client not found".to_string(), + })?; + + let payment_connector = &payment_attempt.connector.clone().ok_or( + errors::ApiErrorResponse::GenericNotFoundError { + message: "unable to derive payment connector from payment attempt".to_string(), + }, + )?; - contract_based_routing_config - .label_info - .as_ref() - .map(|label_info_vec| { - for label_info in label_info_vec { - if Some(&label_info.mca_id) == payment_attempt.merchant_connector_id.as_ref() { - existing_label_info = Some(label_info.clone()); + let contract_based_routing_config = + fetch_dynamic_routing_configs::( + state, + profile_id, + contract_routing_algo_ref + .algorithm_id_with_timestamp + .algorithm_id + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract_routing algorithm_id not found".to_string(), + }) + .attach_printable( + "contract_based_routing_algorithm_id not found in business_profile", + )?, + ) + .await + .change_context(errors::ApiErrorResponse::GenericNotFoundError { + message: "contract based dynamic routing configs not found".to_string(), + }) + .attach_printable("unable to retrieve contract based dynamic routing configs")?; + + let mut existing_label_info = None; + + contract_based_routing_config + .label_info + .as_ref() + .map(|label_info_vec| { + for label_info in label_info_vec { + if Some(&label_info.mca_id) + == payment_attempt.merchant_connector_id.as_ref() + { + existing_label_info = Some(label_info.clone()); + } } - } - }); - - let final_label_info = existing_label_info - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable("unable to get LabelInformation from ContractBasedRoutingConfig")?; - - logger::debug!( - "contract based routing: matched LabelInformation - {:?}", - final_label_info - ); - - let request_label_info = routing_types::LabelInformation { - label: format!( - "{}:{}", - final_label_info.label.clone(), - final_label_info.mca_id.get_string_repr() - ), - target_count: final_label_info.target_count, - target_time: final_label_info.target_time, - mca_id: final_label_info.mca_id.to_owned(), - }; + }); - let payment_status_attribute = - get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + let final_label_info = existing_label_info + .ok_or(errors::ApiErrorResponse::GenericNotFoundError { + message: "LabelInformation from ContractBasedRoutingConfig not found" + .to_string(), + }) + .attach_printable( + "unable to get LabelInformation from ContractBasedRoutingConfig", + )?; - if payment_status_attribute == common_enums::AttemptStatus::Charged { - client - .update_contracts( + logger::debug!( + "contract based routing: matched LabelInformation - {:?}", + final_label_info + ); + + let request_label_info = routing_types::LabelInformation { + label: format!( + "{}:{}", + final_label_info.label.clone(), + final_label_info.mca_id.get_string_repr() + ), + target_count: final_label_info.target_count, + target_time: final_label_info.target_time, + mca_id: final_label_info.mca_id.to_owned(), + }; + + let payment_status_attribute = + get_desired_payment_status_for_dynamic_routing_metrics(payment_attempt.status); + + if payment_status_attribute == common_enums::AttemptStatus::Charged { + client + .update_contracts( + profile_id.get_string_repr().into(), + vec![request_label_info], + "".to_string(), + vec![], + state.get_grpc_headers(), + ) + .await + .change_context(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to update contract based routing window in dynamic routing service", + )?; + } + + let contract_scores = client + .calculate_contract_score( profile_id.get_string_repr().into(), - vec![request_label_info], + contract_based_routing_config.clone(), "".to_string(), - vec![], + routable_connectors.clone(), state.get_grpc_headers(), ) .await .change_context(errors::ApiErrorResponse::InternalServerError) .attach_printable( - "unable to update contract based routing window in dynamic routing service", + "unable to calculate/fetch contract scores from dynamic routing service", )?; - } - let contract_scores = client - .calculate_contract_score( - profile_id.get_string_repr().into(), - contract_based_routing_config.clone(), - "".to_string(), - routable_connectors.clone(), - state.get_grpc_headers(), - ) - .await - .change_context(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to calculate/fetch contract scores from dynamic routing service", - )?; - - let first_contract_based_connector = &contract_scores - .labels_with_score - .first() - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable( - "unable to fetch the first connector from list of connectors obtained from dynamic routing service", - )?; + let first_contract_based_connector = &contract_scores + .labels_with_score + .first() + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable( + "unable to fetch the first connector from list of connectors obtained from dynamic routing service", + )?; - let (first_contract_based_connector, connector_score, current_payment_cnt) = (first_contract_based_connector.label - .split_once(':') - .ok_or(errors::ApiErrorResponse::InternalServerError) - .attach_printable(format!( - "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", - first_contract_based_connector - ))? - .0, first_contract_based_connector.score, first_contract_based_connector.current_count ); - - core_metrics::DYNAMIC_CONTRACT_BASED_ROUTING.add( - 1, - router_env::metric_attributes!( - ( - "tenant", - state.tenant.tenant_id.get_string_repr().to_owned(), - ), - ( - "merchant_profile_id", - format!( - "{}:{}", - payment_attempt.merchant_id.get_string_repr(), - payment_attempt.profile_id.get_string_repr() + let (first_contract_based_connector, connector_score, current_payment_cnt) = (first_contract_based_connector.label + .split_once(':') + .ok_or(errors::ApiErrorResponse::InternalServerError) + .attach_printable(format!( + "unable to split connector_name and mca_id from the first connector {:?} obtained from dynamic routing service", + first_contract_based_connector + ))? + .0, first_contract_based_connector.score, first_contract_based_connector.current_count ); + + core_metrics::DYNAMIC_CONTRACT_BASED_ROUTING.add( + 1, + router_env::metric_attributes!( + ( + "tenant", + state.tenant.tenant_id.get_string_repr().to_owned(), ), - ), - ( - "contract_based_routing_connector", - first_contract_based_connector.to_string(), - ), - ( - "contract_based_routing_connector_score", - connector_score.to_string(), - ), - ( - "current_payment_count_contract_based_routing_connector", - current_payment_cnt.to_string(), - ), - ("payment_connector", payment_connector.to_string()), - ( - "currency", - payment_attempt - .currency - .map_or_else(|| "None".to_string(), |currency| currency.to_string()), - ), - ( - "payment_method", - payment_attempt.payment_method.map_or_else( - || "None".to_string(), - |payment_method| payment_method.to_string(), + ( + "merchant_profile_id", + format!( + "{}:{}", + payment_attempt.merchant_id.get_string_repr(), + payment_attempt.profile_id.get_string_repr() + ), ), - ), - ( - "payment_method_type", - payment_attempt.payment_method_type.map_or_else( - || "None".to_string(), - |payment_method_type| payment_method_type.to_string(), + ( + "contract_based_routing_connector", + first_contract_based_connector.to_string(), ), - ), - ( - "capture_method", - payment_attempt.capture_method.map_or_else( - || "None".to_string(), - |capture_method| capture_method.to_string(), + ( + "contract_based_routing_connector_score", + connector_score.to_string(), ), - ), - ( - "authentication_type", - payment_attempt.authentication_type.map_or_else( - || "None".to_string(), - |authentication_type| authentication_type.to_string(), + ( + "current_payment_count_contract_based_routing_connector", + current_payment_cnt.to_string(), + ), + ("payment_connector", payment_connector.to_string()), + ( + "currency", + payment_attempt + .currency + .map_or_else(|| "None".to_string(), |currency| currency.to_string()), ), + ( + "payment_method", + payment_attempt.payment_method.map_or_else( + || "None".to_string(), + |payment_method| payment_method.to_string(), + ), + ), + ( + "payment_method_type", + payment_attempt.payment_method_type.map_or_else( + || "None".to_string(), + |payment_method_type| payment_method_type.to_string(), + ), + ), + ( + "capture_method", + payment_attempt.capture_method.map_or_else( + || "None".to_string(), + |capture_method| capture_method.to_string(), + ), + ), + ( + "authentication_type", + payment_attempt.authentication_type.map_or_else( + || "None".to_string(), + |authentication_type| authentication_type.to_string(), + ), + ), + ("payment_status", payment_attempt.status.to_string()), ), - ("payment_status", payment_attempt.status.to_string()), - ), - ); - logger::debug!("successfully pushed contract_based_routing metrics"); + ); + logger::debug!("successfully pushed contract_based_routing metrics"); - Ok(()) + Ok(()) + } else { + Ok(()) + } } else { Ok(()) } From 57ab8693e26a54cd5fe73b28459c9b03235e5d5b Mon Sep 17 00:00:00 2001 From: Sai Harsha Vardhan <56996463+sai-harsha-vardhan@users.noreply.github.com> Date: Sat, 22 Feb 2025 00:26:54 +0530 Subject: [PATCH 126/133] feat(router): add `merchant_configuration_id` in netcetera metadata and make other merchant configurations optional (#7347) --- crates/connector_configs/src/common_config.rs | 1 + crates/connector_configs/src/connector.rs | 1 + .../connector_configs/toml/development.toml | 16 +++++++--- crates/connector_configs/toml/production.toml | 16 +++++++--- crates/connector_configs/toml/sandbox.toml | 16 +++++++--- .../src/connector/netcetera/transformers.rs | 23 ++++++------- crates/router/src/core/payments/helpers.rs | 32 ++++++++++++------- .../payments/operations/payment_confirm.rs | 4 +-- 8 files changed, 70 insertions(+), 39 deletions(-) diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index 599d22ae3f6..ebb5870655c 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -107,6 +107,7 @@ pub struct ApiModelMetaData { pub locale: Option, pub card_brands: Option>, pub merchant_category_code: Option, + pub merchant_configuration_id: Option, } #[serde_with::skip_serializing_none] diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 8f1deb02c53..2d1d2e4ede8 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -118,6 +118,7 @@ pub struct ConfigMetadata { pub locale: Option, pub card_brands: Option, pub merchant_category_code: Option, + pub merchant_configuration_id: Option, } #[serde_with::skip_serializing_none] diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 266ae918779..ba54730681f 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -4103,7 +4103,7 @@ private_key="Base64 encoded PEM formatted private key" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.endpoint_prefix] name="endpoint_prefix" @@ -4115,25 +4115,31 @@ type="Text" name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" [taxjar] diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index e4c0e70e191..88bba56c820 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -3041,31 +3041,37 @@ type="Text" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.merchant_country_code] name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" [taxjar] diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index b7b57690474..265f06ee749 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -4045,31 +4045,37 @@ type="Text" name="mcc" label="MCC" placeholder="Enter MCC" -required=true +required=false type="Text" [netcetera.metadata.merchant_country_code] name="merchant_country_code" label="3 digit numeric country code" placeholder="Enter 3 digit numeric country code" -required=true +required=false type="Text" [netcetera.metadata.merchant_name] name="merchant_name" label="Name of the merchant" placeholder="Enter Name of the merchant" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_name] name="three_ds_requestor_name" label="ThreeDS requestor name" placeholder="Enter ThreeDS requestor name" -required=true +required=false type="Text" [netcetera.metadata.three_ds_requestor_id] name="three_ds_requestor_id" label="ThreeDS request id" placeholder="Enter ThreeDS request id" -required=true +required=false +type="Text" +[netcetera.metadata.merchant_configuration_id] +name="merchant_configuration_id" +label="Merchant Configuration ID" +placeholder="Enter Merchant Configuration ID" +required=false type="Text" diff --git a/crates/router/src/connector/netcetera/transformers.rs b/crates/router/src/connector/netcetera/transformers.rs index ab228d95a9e..ab6002ba312 100644 --- a/crates/router/src/connector/netcetera/transformers.rs +++ b/crates/router/src/connector/netcetera/transformers.rs @@ -253,12 +253,13 @@ pub struct NetceteraErrorDetails { #[derive(Debug, Serialize, Deserialize)] pub struct NetceteraMetaData { - pub mcc: String, - pub merchant_country_code: String, - pub merchant_name: String, + pub mcc: Option, + pub merchant_country_code: Option, + pub merchant_name: Option, pub endpoint_prefix: String, - pub three_ds_requestor_name: String, - pub three_ds_requestor_id: String, + pub three_ds_requestor_name: Option, + pub three_ds_requestor_id: Option, + pub merchant_configuration_id: Option, } impl TryFrom<&Option> for NetceteraMetaData { @@ -515,13 +516,13 @@ impl TryFrom<&NetceteraRouterData<&types::authentication::ConnectorAuthenticatio .parse_value("NetceteraMetaData") .change_context(errors::ConnectorError::RequestEncodingFailed)?; let merchant_data = netcetera_types::MerchantData { - merchant_configuration_id: None, - mcc: Some(connector_meta_data.mcc), - merchant_country_code: Some(connector_meta_data.merchant_country_code), - merchant_name: Some(connector_meta_data.merchant_name), + merchant_configuration_id: connector_meta_data.merchant_configuration_id, + mcc: connector_meta_data.mcc, + merchant_country_code: connector_meta_data.merchant_country_code, + merchant_name: connector_meta_data.merchant_name, notification_url: request.return_url.clone(), - three_ds_requestor_id: Some(connector_meta_data.three_ds_requestor_id), - three_ds_requestor_name: Some(connector_meta_data.three_ds_requestor_name), + three_ds_requestor_id: connector_meta_data.three_ds_requestor_id, + three_ds_requestor_name: connector_meta_data.three_ds_requestor_name, white_list_status: None, trust_list_status: None, seller_info: None, diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index 1803e12f0b2..c995b646ffc 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -6539,7 +6539,7 @@ pub fn validate_mandate_data_and_future_usage( pub enum UnifiedAuthenticationServiceFlow { ClickToPayInitiate, ExternalAuthenticationInitiate { - acquirer_details: authentication::types::AcquirerDetails, + acquirer_details: Option, card_number: ::cards::CardNumber, token: String, }, @@ -6613,7 +6613,7 @@ pub async fn decide_action_for_unified_authentication_service( pub enum PaymentExternalAuthenticationFlow { PreAuthenticationFlow { - acquirer_details: authentication::types::AcquirerDetails, + acquirer_details: Option, card_number: ::cards::CardNumber, token: String, }, @@ -6690,17 +6690,27 @@ pub async fn get_payment_external_authentication_flow_during_confirm( connector_data.merchant_connector_id.as_ref(), ) .await?; - let acquirer_details: authentication::types::AcquirerDetails = payment_connector_mca + let acquirer_details = payment_connector_mca .get_metadata() - .get_required_value("merchant_connector_account.metadata")? - .peek() .clone() - .parse_value("AcquirerDetails") - .change_context(errors::ApiErrorResponse::PreconditionFailed { - message: - "acquirer_bin and acquirer_merchant_id not found in Payment Connector's Metadata" - .to_string(), - })?; + .and_then(|metadata| { + metadata + .peek() + .clone() + .parse_value::("AcquirerDetails") + .change_context(errors::ApiErrorResponse::PreconditionFailed { + message: + "acquirer_bin and acquirer_merchant_id not found in Payment Connector's Metadata" + .to_string(), + }) + .inspect_err(|err| { + logger::error!( + "Failed to parse acquirer details from Payment Connector's Metadata: {:?}", + err + ); + }) + .ok() + }); Some(PaymentExternalAuthenticationFlow::PreAuthenticationFlow { card_number, token, diff --git a/crates/router/src/core/payments/operations/payment_confirm.rs b/crates/router/src/core/payments/operations/payment_confirm.rs index 6068eba1319..43057855467 100644 --- a/crates/router/src/core/payments/operations/payment_confirm.rs +++ b/crates/router/src/core/payments/operations/payment_confirm.rs @@ -983,7 +983,7 @@ impl Domain> for card_number, token, business_profile, - Some(acquirer_details), + acquirer_details, Some(payment_data.payment_attempt.payment_id.clone()), payment_data.payment_attempt.organization_id.clone(), ) @@ -1267,7 +1267,7 @@ impl Domain> for state, pre_auth_response, authentication.clone(), - Some(acquirer_details), + acquirer_details, ).await?; payment_data.authentication = Some(updated_authentication.clone()); From 1a7ee233db174ebd66173d5feecd0594b5901e36 Mon Sep 17 00:00:00 2001 From: spritianeja03 <146620839+spritianeja03@users.noreply.github.com> Date: Sat, 22 Feb 2025 01:07:06 +0530 Subject: [PATCH 127/133] ci: update creds (#7345) Co-authored-by: Spriti Aneja --- .github/workflows/cypress-tests-runner.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/cypress-tests-runner.yml b/.github/workflows/cypress-tests-runner.yml index dbef78355de..44a0baa8d34 100644 --- a/.github/workflows/cypress-tests-runner.yml +++ b/.github/workflows/cypress-tests-runner.yml @@ -128,7 +128,7 @@ jobs: CONNECTOR_AUTH_PASSPHRASE: ${{ secrets.CONNECTOR_AUTH_PASSPHRASE }} CONNECTOR_CREDS_S3_BUCKET_URI: ${{ secrets.CONNECTOR_CREDS_S3_BUCKET_URI}} DESTINATION_FILE_NAME: "creds.json.gpg" - S3_SOURCE_FILE_NAME: "5b3ebdad-1067-421b-83e5-3ceb73c3eeeb.json.gpg" + S3_SOURCE_FILE_NAME: "57a07166-894a-4f43-b4b5-ee1155138ec9.json.gpg" shell: bash run: | mkdir -p ".github/secrets" ".github/test" From c14519ebd958abc79879244f8180686b2be30d31 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Sat, 22 Feb 2025 13:23:33 +0530 Subject: [PATCH 128/133] feat(samsung_pay): collect customer address details form Samsung Pay based on business profile config and connector required fields (#7320) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 12 +- api-reference/openapi_spec.json | 12 +- crates/api_models/src/payments.rs | 4 + .../src/core/payments/flows/session_flow.rs | 164 +++++++++++++----- 4 files changed, 144 insertions(+), 48 deletions(-) diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 2ea34e84690..06796b4486b 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -20288,7 +20288,9 @@ "merchant", "amount", "protocol", - "allowed_brands" + "allowed_brands", + "billing_address_required", + "shipping_address_required" ], "properties": { "version": { @@ -20318,6 +20320,14 @@ "type": "string" }, "description": "List of supported card brands" + }, + "billing_address_required": { + "type": "boolean", + "description": "Is billing address required to be collected from wallet" + }, + "shipping_address_required": { + "type": "boolean", + "description": "Is shipping address required to be collected from wallet" } } }, diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 78760af592a..632d800f47e 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -24860,7 +24860,9 @@ "merchant", "amount", "protocol", - "allowed_brands" + "allowed_brands", + "billing_address_required", + "shipping_address_required" ], "properties": { "version": { @@ -24890,6 +24892,14 @@ "type": "string" }, "description": "List of supported card brands" + }, + "billing_address_required": { + "type": "boolean", + "description": "Is billing address required to be collected from wallet" + }, + "shipping_address_required": { + "type": "boolean", + "description": "Is shipping address required to be collected from wallet" } } }, diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index b567bc52286..faee2e38877 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -6861,6 +6861,10 @@ pub struct SamsungPaySessionTokenResponse { pub protocol: SamsungPayProtocolType, /// List of supported card brands pub allowed_brands: Vec, + /// Is billing address required to be collected from wallet + pub billing_address_required: bool, + /// Is shipping address required to be collected from wallet + pub shipping_address_required: bool, } #[derive(Debug, Clone, Eq, PartialEq, serde::Serialize, ToSchema)] diff --git a/crates/router/src/core/payments/flows/session_flow.rs b/crates/router/src/core/payments/flows/session_flow.rs index f6e5d1e7f05..662453c4a3e 100644 --- a/crates/router/src/core/payments/flows/session_flow.rs +++ b/crates/router/src/core/payments/flows/session_flow.rs @@ -610,8 +610,11 @@ fn create_paze_session_token( } fn create_samsung_pay_session_token( + state: &routes::SessionState, router_data: &types::PaymentsSessionRouterData, header_payload: hyperswitch_domain_models::payments::HeaderPayload, + connector: &api::ConnectorData, + business_profile: &domain::Profile, ) -> RouterResult { let samsung_pay_session_token_data = router_data .connector_wallets_details @@ -657,6 +660,20 @@ fn create_samsung_pay_session_token( let formatted_payment_id = router_data.payment_id.replace("_", "-"); + let billing_address_required = is_billing_address_required_to_be_collected_from_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::SamsungPay, + ); + + let shipping_address_required = is_shipping_address_required_to_be_collected_form_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::SamsungPay, + ); + Ok(types::PaymentsSessionRouterData { response: Ok(types::PaymentsResponseData::SessionResponse { session_token: payment_types::SessionToken::SamsungPay(Box::new( @@ -677,6 +694,8 @@ fn create_samsung_pay_session_token( }, protocol: payment_types::SamsungPayProtocolType::Protocol3ds, allowed_brands: samsung_pay_wallet_details.allowed_brands, + billing_address_required, + shipping_address_required, }, )), }), @@ -684,6 +703,86 @@ fn create_samsung_pay_session_token( }) } +/// Function to determine whether the billing address is required to be collected from the wallet, +/// based on business profile settings, the payment method type, and the connector's required fields +/// for the specific payment method. +/// +/// If `always_collect_billing_details_from_wallet_connector` is enabled, it indicates that the +/// billing address is always required to be collected from the wallet. +/// +/// If only `collect_billing_details_from_wallet_connector` is enabled, the billing address will be +/// collected only if the connector required fields for the specific payment method type contain +/// the billing fields. +fn is_billing_address_required_to_be_collected_from_wallet( + state: &routes::SessionState, + connector: &api::ConnectorData, + business_profile: &domain::Profile, + payment_method_type: enums::PaymentMethodType, +) -> bool { + let always_collect_billing_details_from_wallet_connector = business_profile + .always_collect_billing_details_from_wallet_connector + .unwrap_or(false); + + if always_collect_billing_details_from_wallet_connector { + always_collect_billing_details_from_wallet_connector + } else if business_profile + .collect_billing_details_from_wallet_connector + .unwrap_or(false) + { + let billing_variants = enums::FieldType::get_billing_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + payment_method_type, + connector.connector_name, + billing_variants, + ) + } else { + false + } +} + +/// Function to determine whether the shipping address is required to be collected from the wallet, +/// based on business profile settings, the payment method type, and the connector required fields +/// for the specific payment method type. +/// +/// If `always_collect_shipping_details_from_wallet_connector` is enabled, it indicates that the +/// shipping address is always required to be collected from the wallet. +/// +/// If only `collect_shipping_details_from_wallet_connector` is enabled, the shipping address will be +/// collected only if the connector required fields for the specific payment method type contain +/// the shipping fields. +fn is_shipping_address_required_to_be_collected_form_wallet( + state: &routes::SessionState, + connector: &api::ConnectorData, + business_profile: &domain::Profile, + payment_method_type: enums::PaymentMethodType, +) -> bool { + let always_collect_shipping_details_from_wallet_connector = business_profile + .always_collect_shipping_details_from_wallet_connector + .unwrap_or(false); + + if always_collect_shipping_details_from_wallet_connector { + always_collect_shipping_details_from_wallet_connector + } else if business_profile + .collect_shipping_details_from_wallet_connector + .unwrap_or(false) + { + let shipping_variants = enums::FieldType::get_shipping_variants(); + + is_dynamic_fields_required( + &state.conf.required_fields, + enums::PaymentMethod::Wallet, + payment_method_type, + connector.connector_name, + shipping_variants, + ) + } else { + false + } +} + fn get_session_request_for_simplified_apple_pay( apple_pay_merchant_identifier: String, session_token_data: payment_types::SessionTokenForSimplifiedApplePay, @@ -865,27 +964,12 @@ fn create_gpay_session_token( ..router_data.clone() }) } else { - let always_collect_billing_details_from_wallet_connector = business_profile - .always_collect_billing_details_from_wallet_connector - .unwrap_or(false); - - let is_billing_details_required = if always_collect_billing_details_from_wallet_connector { - always_collect_billing_details_from_wallet_connector - } else if business_profile - .collect_billing_details_from_wallet_connector - .unwrap_or(false) - { - let billing_variants = enums::FieldType::get_billing_variants(); - is_dynamic_fields_required( - &state.conf.required_fields, - enums::PaymentMethod::Wallet, - enums::PaymentMethodType::GooglePay, - connector.connector_name, - billing_variants, - ) - } else { - false - }; + let is_billing_details_required = is_billing_address_required_to_be_collected_from_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::GooglePay, + ); let required_amount_type = StringMajorUnitForConnector; let google_pay_amount = required_amount_type @@ -904,29 +988,13 @@ fn create_gpay_session_token( total_price: google_pay_amount, }; - let always_collect_shipping_details_from_wallet_connector = business_profile - .always_collect_shipping_details_from_wallet_connector - .unwrap_or(false); - let required_shipping_contact_fields = - if always_collect_shipping_details_from_wallet_connector { - true - } else if business_profile - .collect_shipping_details_from_wallet_connector - .unwrap_or(false) - { - let shipping_variants = enums::FieldType::get_shipping_variants(); - - is_dynamic_fields_required( - &state.conf.required_fields, - enums::PaymentMethod::Wallet, - enums::PaymentMethodType::GooglePay, - connector.connector_name, - shipping_variants, - ) - } else { - false - }; + is_shipping_address_required_to_be_collected_form_wallet( + state, + connector, + business_profile, + enums::PaymentMethodType::GooglePay, + ); if connector_wallets_details.google_pay.is_some() { let gpay_data = router_data @@ -1199,9 +1267,13 @@ impl RouterDataSession for types::PaymentsSessionRouterData { api::GetToken::GpayMetadata => { create_gpay_session_token(state, self, connector, business_profile) } - api::GetToken::SamsungPayMetadata => { - create_samsung_pay_session_token(self, header_payload) - } + api::GetToken::SamsungPayMetadata => create_samsung_pay_session_token( + state, + self, + header_payload, + connector, + business_profile, + ), api::GetToken::ApplePayMetadata => { create_applepay_session_token( state, From 890a265e7b1b06aeea100994efbebe3ce898a125 Mon Sep 17 00:00:00 2001 From: Kartikeya Hegde Date: Sat, 22 Feb 2025 13:23:51 +0530 Subject: [PATCH 129/133] chore(masking): add peek_mut to PeekInterface (#7281) --- crates/masking/src/abs.rs | 3 +++ crates/masking/src/bytes.rs | 4 ++++ crates/masking/src/secret.rs | 4 ++++ crates/masking/src/strong_secret.rs | 4 ++++ 4 files changed, 15 insertions(+) diff --git a/crates/masking/src/abs.rs b/crates/masking/src/abs.rs index 6501f89c9db..8996db65f23 100644 --- a/crates/masking/src/abs.rs +++ b/crates/masking/src/abs.rs @@ -6,6 +6,9 @@ use crate::Secret; pub trait PeekInterface { /// Only method providing access to the secret value. fn peek(&self) -> &S; + + /// Provide a mutable reference to the inner value. + fn peek_mut(&mut self) -> &mut S; } /// Interface that consumes a option secret and returns the value. diff --git a/crates/masking/src/bytes.rs b/crates/masking/src/bytes.rs index e828f8a78e0..07b8c289ad8 100644 --- a/crates/masking/src/bytes.rs +++ b/crates/masking/src/bytes.rs @@ -28,6 +28,10 @@ impl PeekInterface for SecretBytesMut { fn peek(&self) -> &BytesMut { &self.0 } + + fn peek_mut(&mut self) -> &mut BytesMut { + &mut self.0 + } } impl fmt::Debug for SecretBytesMut { diff --git a/crates/masking/src/secret.rs b/crates/masking/src/secret.rs index 7f7c18094a0..ee65b1c015b 100644 --- a/crates/masking/src/secret.rs +++ b/crates/masking/src/secret.rs @@ -95,6 +95,10 @@ where fn peek(&self) -> &SecretValue { &self.inner_secret } + + fn peek_mut(&mut self) -> &mut SecretValue { + &mut self.inner_secret + } } impl From for Secret diff --git a/crates/masking/src/strong_secret.rs b/crates/masking/src/strong_secret.rs index 300b5463d25..43d2c97dfb3 100644 --- a/crates/masking/src/strong_secret.rs +++ b/crates/masking/src/strong_secret.rs @@ -32,6 +32,10 @@ impl PeekInterface fn peek(&self) -> &Secret { &self.inner_secret } + + fn peek_mut(&mut self) -> &mut Secret { + &mut self.inner_secret + } } impl From From 9bc8fd4d8c4be1b54050398dfb3b574e924e4b5f Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Sat, 22 Feb 2025 13:25:37 +0530 Subject: [PATCH 130/133] feat(connector): add Samsung pay mandate support for Cybersource (#7298) --- crates/connector_configs/toml/sandbox.toml | 2 + .../src/connectors/cybersource.rs | 1 + .../connectors/cybersource/transformers.rs | 53 +++++++----- .../payment_connector_required_fields.rs | 81 ++++++++++++++++++- 4 files changed, 116 insertions(+), 21 deletions(-) diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 265f06ee749..db440656fba 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1215,6 +1215,8 @@ merchant_secret="Source verification key" payment_method_type = "google_pay" [[cybersource.wallet]] payment_method_type = "paze" +[[cybersource.wallet]] + payment_method_type = "samsung_pay" [cybersource.connector_auth.SignatureKey] api_key="Key" key1="Merchant ID" diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource.rs b/crates/hyperswitch_connectors/src/connectors/cybersource.rs index 5a60f6d705a..812d0391dec 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource.rs @@ -312,6 +312,7 @@ impl ConnectorValidation for Cybersource { PaymentMethodDataType::Card, PaymentMethodDataType::ApplePay, PaymentMethodDataType::GooglePay, + PaymentMethodDataType::SamsungPay, ]); utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } diff --git a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs index 20db1648e96..4c65218c5a3 100644 --- a/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/cybersource/transformers.rs @@ -251,6 +251,11 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { )), Some(PaymentSolution::GooglePay), ), + WalletData::SamsungPay(samsung_pay_data) => ( + (get_samsung_pay_payment_information(&samsung_pay_data) + .attach_printable("Failed to get samsung pay payment information")?), + Some(PaymentSolution::SamsungPay), + ), WalletData::AliPayQr(_) | WalletData::AliPayRedirect(_) | WalletData::AliPayHkRedirect(_) @@ -269,7 +274,6 @@ impl TryFrom<&SetupMandateRouterData> for CybersourceZeroMandateRequest { | WalletData::PaypalRedirect(_) | WalletData::PaypalSdk(_) | WalletData::Paze(_) - | WalletData::SamsungPay(_) | WalletData::TwintRedirect {} | WalletData::VippsRedirect {} | WalletData::TouchNGoRedirect(_) @@ -1859,25 +1863,8 @@ impl let bill_to = build_bill_to(item.router_data.get_optional_billing(), email)?; let order_information = OrderInformationWithBill::from((item, Some(bill_to))); - let samsung_pay_fluid_data_value = - get_samsung_pay_fluid_data_value(&samsung_pay_data.payment_credential.token_data)?; - - let samsung_pay_fluid_data_str = serde_json::to_string(&samsung_pay_fluid_data_value) - .change_context(errors::ConnectorError::RequestEncodingFailed) - .attach_printable("Failed to serialize samsung pay fluid data")?; - - let payment_information = - PaymentInformation::SamsungPay(Box::new(SamsungPayPaymentInformation { - fluid_data: FluidData { - value: Secret::new(consts::BASE64_ENGINE.encode(samsung_pay_fluid_data_str)), - descriptor: Some( - consts::BASE64_ENGINE.encode(FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY), - ), - }, - tokenized_card: SamsungPayTokenizedCard { - transaction_type: TransactionType::SamsungPay, - }, - })); + let payment_information = get_samsung_pay_payment_information(&samsung_pay_data) + .attach_printable("Failed to get samsung pay payment information")?; let processing_information = ProcessingInformation::try_from(( item, @@ -1903,6 +1890,32 @@ impl } } +fn get_samsung_pay_payment_information( + samsung_pay_data: &SamsungPayWalletData, +) -> Result> { + let samsung_pay_fluid_data_value = + get_samsung_pay_fluid_data_value(&samsung_pay_data.payment_credential.token_data)?; + + let samsung_pay_fluid_data_str = serde_json::to_string(&samsung_pay_fluid_data_value) + .change_context(errors::ConnectorError::RequestEncodingFailed) + .attach_printable("Failed to serialize samsung pay fluid data")?; + + let payment_information = + PaymentInformation::SamsungPay(Box::new(SamsungPayPaymentInformation { + fluid_data: FluidData { + value: Secret::new(consts::BASE64_ENGINE.encode(samsung_pay_fluid_data_str)), + descriptor: Some( + consts::BASE64_ENGINE.encode(FLUID_DATA_DESCRIPTOR_FOR_SAMSUNG_PAY), + ), + }, + tokenized_card: SamsungPayTokenizedCard { + transaction_type: TransactionType::SamsungPay, + }, + })); + + Ok(payment_information) +} + fn get_samsung_pay_fluid_data_value( samsung_pay_token_data: &hyperswitch_domain_models::payment_method_data::SamsungPayTokenData, ) -> Result> { diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 27cc485188b..3849054045c 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -9128,7 +9128,86 @@ impl Default for settings::RequiredFields { RequiredFieldFinal { mandate: HashMap::new(), non_mandate: HashMap::new(), - common: HashMap::new(), + common: HashMap::from( + [ + ( + "billing.email".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.email".to_string(), + display_name: "email".to_string(), + field_type: enums::FieldType::UserEmailAddress, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "billing_first_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.state".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.state".to_string(), + display_name: "state".to_string(), + field_type: enums::FieldType::UserAddressState, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.country".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.country".to_string(), + display_name: "country".to_string(), + field_type: enums::FieldType::UserAddressCountry{ + options: vec![ + "ALL".to_string(), + ] + }, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ) + ] + ), } ), ]), From 11ff437456f9d97205ce07e4d4df63006f3ad0c6 Mon Sep 17 00:00:00 2001 From: Shankar Singh C <83439957+ShankarSinghC@users.noreply.github.com> Date: Sat, 22 Feb 2025 17:11:57 +0530 Subject: [PATCH 131/133] fix(samsung_pay): add payment_method_type duplication check (#7337) Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- crates/common_enums/src/enums.rs | 6 ++++++ crates/router/src/core/payments/helpers.rs | 11 +++++++---- crates/router/src/core/payments/tokenization.rs | 8 +++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 54514f6e05a..c286ae04d5f 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -1651,6 +1651,12 @@ pub enum PaymentMethodType { DirectCarrierBilling, } +impl PaymentMethodType { + pub fn should_check_for_customer_saved_payment_method_type(self) -> bool { + matches!(self, Self::ApplePay | Self::GooglePay | Self::SamsungPay) + } +} + impl masking::SerializableSecret for PaymentMethodType {} /// Indicates the type of payment method. Eg: 'card', 'wallet', etc. diff --git a/crates/router/src/core/payments/helpers.rs b/crates/router/src/core/payments/helpers.rs index c995b646ffc..032262b30aa 100644 --- a/crates/router/src/core/payments/helpers.rs +++ b/crates/router/src/core/payments/helpers.rs @@ -591,10 +591,13 @@ pub async fn get_token_pm_type_mandate_details( mandate_generic_data.mandate_connector, mandate_generic_data.payment_method_info, ) - } else if request.payment_method_type - == Some(api_models::enums::PaymentMethodType::ApplePay) - || request.payment_method_type - == Some(api_models::enums::PaymentMethodType::GooglePay) + } else if request + .payment_method_type + .map(|payment_method_type_value| { + payment_method_type_value + .should_check_for_customer_saved_payment_method_type() + }) + .unwrap_or(false) { let payment_request_customer_id = request.get_customer_id(); if let Some(customer_id) = diff --git a/crates/router/src/core/payments/tokenization.rs b/crates/router/src/core/payments/tokenization.rs index d46977d8c99..b4e4db3d161 100644 --- a/crates/router/src/core/payments/tokenization.rs +++ b/crates/router/src/core/payments/tokenization.rs @@ -655,9 +655,11 @@ where }, None => { let customer_saved_pm_option = if payment_method_type - == Some(api_models::enums::PaymentMethodType::ApplePay) - || payment_method_type - == Some(api_models::enums::PaymentMethodType::GooglePay) + .map(|payment_method_type_value| { + payment_method_type_value + .should_check_for_customer_saved_payment_method_type() + }) + .unwrap_or(false) { match state .store From 0688972814cf03edbff4bf125a59c338a7e49593 Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Sun, 23 Feb 2025 02:28:10 +0530 Subject: [PATCH 132/133] feat(connector): Add support for passive churn recovery webhooks (#7109) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- api-reference-v2/openapi_spec.json | 48 ++- api-reference/openapi_spec.json | 7 + crates/api_models/Cargo.toml | 1 + crates/api_models/src/payments.rs | 42 ++- crates/api_models/src/webhooks.rs | 35 ++ crates/common_enums/src/enums.rs | 25 ++ crates/common_utils/src/ext_traits.rs | 52 ++- crates/diesel_models/src/payment_attempt.rs | 24 ++ crates/diesel_models/src/schema_v2.rs | 1 + crates/hyperswitch_domain_models/Cargo.toml | 1 + crates/hyperswitch_domain_models/src/lib.rs | 2 + .../src/payments/payment_attempt.rs | 56 ++++ .../src/revenue_recovery.rs | 190 +++++++++++ crates/hyperswitch_interfaces/Cargo.toml | 2 + crates/hyperswitch_interfaces/src/webhooks.rs | 29 ++ crates/openapi/src/openapi.rs | 1 + crates/openapi/src/openapi_v2.rs | 3 + crates/router/Cargo.toml | 3 +- crates/router/src/core/errors.rs | 17 + crates/router/src/core/payments.rs | 2 +- .../router/src/core/payments/transformers.rs | 53 ++- crates/router/src/core/webhooks.rs | 2 + .../router/src/core/webhooks/incoming_v2.rs | 20 ++ .../src/core/webhooks/recovery_incoming.rs | 307 ++++++++++++++++++ crates/router/src/routes.rs | 3 + crates/router/src/routes/app.rs | 12 + crates/router/src/routes/lock_utils.rs | 3 +- crates/router/src/routes/recovery_webhooks.rs | 53 +++ .../connector_integration_interface.rs | 28 ++ crates/router_env/src/logger/types.rs | 2 + .../down.sql | 2 + .../up.sql | 2 + 32 files changed, 992 insertions(+), 36 deletions(-) create mode 100644 crates/hyperswitch_domain_models/src/revenue_recovery.rs create mode 100644 crates/router/src/core/webhooks/recovery_incoming.rs create mode 100644 crates/router/src/routes/recovery_webhooks.rs create mode 100644 v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql create mode 100644 v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 06796b4486b..16fcefac0ee 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -13397,6 +13397,19 @@ } } }, + "PaymentAttemptFeatureMetadata": { + "type": "object", + "properties": { + "revenue_recovery": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentAttemptRevenueRecoveryData" + } + ], + "nullable": true + } + } + }, "PaymentAttemptResponse": { "type": "object", "required": [ @@ -13405,7 +13418,8 @@ "amount", "authentication_type", "created_at", - "modified_at" + "modified_at", + "connector_payment_id" ], "properties": { "id": { @@ -13501,9 +13515,7 @@ }, "connector_payment_id": { "type": "string", - "description": "A unique identifier for a payment provided by the connector", - "example": "993672945374576J", - "nullable": true + "description": "A unique identifier for a payment provided by the connector" }, "payment_method_id": { "type": "string", @@ -13520,6 +13532,27 @@ "type": "string", "description": "Value passed in X-CLIENT-VERSION header during payments confirm request by the client", "nullable": true + }, + "feature_metadata": { + "allOf": [ + { + "$ref": "#/components/schemas/PaymentAttemptFeatureMetadata" + } + ], + "nullable": true + } + } + }, + "PaymentAttemptRevenueRecoveryData": { + "type": "object", + "properties": { + "attempt_triggered_by": { + "allOf": [ + { + "$ref": "#/components/schemas/TriggeredBy" + } + ], + "nullable": true } } }, @@ -21526,6 +21559,13 @@ "payout" ] }, + "TriggeredBy": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, "UIWidgetFormLayout": { "type": "string", "enum": [ diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 632d800f47e..8e62ba302ab 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -26144,6 +26144,13 @@ "payout" ] }, + "TriggeredBy": { + "type": "string", + "enum": [ + "internal", + "external" + ] + }, "UIWidgetFormLayout": { "type": "string", "enum": [ diff --git a/crates/api_models/Cargo.toml b/crates/api_models/Cargo.toml index c159af97249..2b84c50bba9 100644 --- a/crates/api_models/Cargo.toml +++ b/crates/api_models/Cargo.toml @@ -22,6 +22,7 @@ customer_v2 = ["common_utils/customer_v2"] payment_methods_v2 = ["common_utils/payment_methods_v2"] dynamic_routing = [] control_center_theme = ["dep:actix-web", "dep:actix-multipart"] +revenue_recovery = [] [dependencies] actix-multipart = { version = "0.6.1", optional = true } diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index faee2e38877..22e845c5e0c 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1511,8 +1511,8 @@ pub struct PaymentAttemptResponse { pub payment_method_subtype: Option, /// A unique identifier for a payment provided by the connector - #[schema(value_type = Option, example = "993672945374576J")] - pub connector_payment_id: Option, + #[schema(value_type = String)] + pub connector_payment_id: Option, /// Identifier for Payment Method used for the payment attempt #[schema(value_type = Option, example = "12345_pm_01926c58bc6e77c09e809964e72af8c8")] @@ -1522,6 +1522,24 @@ pub struct PaymentAttemptResponse { pub client_source: Option, /// Value passed in X-CLIENT-VERSION header during payments confirm request by the client pub client_version: Option, + + /// Additional data that might be required by hyperswitch, to enable some specific features. + pub feature_metadata: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentAttemptFeatureMetadata { + /// Revenue recovery metadata that might be required by hyperswitch. + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive(Clone, Debug, serde::Deserialize, serde::Serialize, PartialEq, ToSchema)] +pub struct PaymentAttemptRevenueRecoveryData { + /// Flag to find out whether an attempt was created by external or internal system. + #[schema(value_type = Option, example = "internal")] + pub attempt_triggered_by: common_enums::TriggeredBy, } #[derive( @@ -5563,6 +5581,26 @@ pub struct PaymentsRetrieveResponse { pub attempts: Option>, } +#[cfg(feature = "v2")] +impl PaymentsRetrieveResponse { + pub fn find_attempt_in_attempts_list_using_connector_transaction_id( + self, + connector_transaction_id: &common_utils::types::ConnectorTransactionId, + ) -> Option { + self.attempts + .as_ref() + .and_then(|attempts| { + attempts.iter().find(|attempt| { + attempt + .connector_payment_id + .as_ref() + .is_some_and(|txn_id| txn_id == connector_transaction_id) + }) + }) + .cloned() + } +} + #[derive(Debug, serde::Deserialize, serde::Serialize, Clone)] #[cfg(feature = "v2")] pub struct PaymentStartRedirectionRequest { diff --git a/crates/api_models/src/webhooks.rs b/crates/api_models/src/webhooks.rs index ddefea542c2..c3e96c47c64 100644 --- a/crates/api_models/src/webhooks.rs +++ b/crates/api_models/src/webhooks.rs @@ -57,6 +57,14 @@ pub enum IncomingWebhookEvent { PayoutExpired, #[cfg(feature = "payouts")] PayoutReversed, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentFailure, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentSuccess, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryPaymentPending, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + RecoveryInvoiceCancel, } pub enum WebhookFlow { @@ -71,6 +79,8 @@ pub enum WebhookFlow { Mandate, ExternalAuthentication, FraudCheck, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + Recovery, } #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -197,6 +207,11 @@ impl From for WebhookFlow { | IncomingWebhookEvent::PayoutCreated | IncomingWebhookEvent::PayoutExpired | IncomingWebhookEvent::PayoutReversed => Self::Payout, + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + IncomingWebhookEvent::RecoveryInvoiceCancel + | IncomingWebhookEvent::RecoveryPaymentFailure + | IncomingWebhookEvent::RecoveryPaymentPending + | IncomingWebhookEvent::RecoveryPaymentSuccess => Self::Recovery, } } } @@ -236,6 +251,14 @@ pub enum ObjectReferenceId { ExternalAuthenticationID(AuthenticationIdType), #[cfg(feature = "payouts")] PayoutId(PayoutIdType), + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + InvoiceId(InvoiceIdType), +} + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +#[derive(Clone)] +pub enum InvoiceIdType { + ConnectorInvoiceId(String), } pub struct IncomingWebhookDetails { @@ -303,3 +326,15 @@ pub struct ConnectorWebhookSecrets { pub secret: Vec, pub additional_secret: Option>, } + +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +impl IncomingWebhookEvent { + pub fn is_recovery_transaction_event(&self) -> bool { + matches!( + self, + Self::RecoveryPaymentFailure + | Self::RecoveryPaymentSuccess + | Self::RecoveryPaymentPending + ) + } +} diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index c286ae04d5f..660474e91ee 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -7591,3 +7591,28 @@ pub enum PaymentConnectorTransmission { /// Payment Connector call succeeded ConnectorCallSucceeded, } + +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + Hash, + PartialEq, + serde::Deserialize, + serde::Serialize, + strum::Display, + strum::EnumString, + ToSchema, +)] +#[router_derive::diesel_enum(storage_type = "db_enum")] +#[strum(serialize_all = "snake_case")] +#[serde(rename_all = "snake_case")] +pub enum TriggeredBy { + /// Denotes payment attempt is been created by internal system. + #[default] + Internal, + /// Denotes payment attempt is been created by external system. + External, +} diff --git a/crates/common_utils/src/ext_traits.rs b/crates/common_utils/src/ext_traits.rs index 945056aafef..6bdeae9b6d9 100644 --- a/crates/common_utils/src/ext_traits.rs +++ b/crates/common_utils/src/ext_traits.rs @@ -4,6 +4,8 @@ use error_stack::ResultExt; use masking::{ExposeInterface, PeekInterface, Secret, Strategy}; use quick_xml::de; +#[cfg(all(feature = "logs", feature = "async_ext"))] +use router_env::logger; use serde::{Deserialize, Serialize}; use crate::{ @@ -295,28 +297,34 @@ impl StringExt for String { /// Extending functionalities of Wrapper types for idiomatic #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -pub trait AsyncExt { +pub trait AsyncExt { /// Output type of the map function type WrappedSelf; /// Extending map by allowing functions which are async - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send; /// Extending the `and_then` by allowing functions which are async - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send; + + /// Extending `unwrap_or_else` to allow async fallback + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send; } #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -impl AsyncExt for Result { +impl AsyncExt for Result { type WrappedSelf = Result; - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send, @@ -327,7 +335,7 @@ impl AsyncExt for Result { } } - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send, @@ -337,13 +345,28 @@ impl AsyncExt for Result { Err(err) => Err(err), } } + + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Ok(a) => a, + Err(_err) => { + #[cfg(feature = "logs")] + logger::error!("Error: {:?}", _err); + func().await + } + } + } } #[cfg(feature = "async_ext")] #[cfg_attr(feature = "async_ext", async_trait::async_trait)] -impl AsyncExt for Option { +impl AsyncExt for Option { type WrappedSelf = Option; - async fn async_and_then(self, func: F) -> Self::WrappedSelf + async fn async_and_then(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future> + Send, @@ -354,7 +377,7 @@ impl AsyncExt for Option { } } - async fn async_map(self, func: F) -> Self::WrappedSelf + async fn async_map(self, func: F) -> Self::WrappedSelf where F: FnOnce(A) -> Fut + Send, Fut: futures::Future + Send, @@ -364,6 +387,17 @@ impl AsyncExt for Option { None => None, } } + + async fn async_unwrap_or_else(self, func: F) -> A + where + F: FnOnce() -> Fut + Send, + Fut: futures::Future + Send, + { + match self { + Some(a) => a, + None => func().await, + } + } } /// Extension trait for validating application configuration. This trait provides utilities to diff --git a/crates/diesel_models/src/payment_attempt.rs b/crates/diesel_models/src/payment_attempt.rs index 8cf6b604b92..9409be94163 100644 --- a/crates/diesel_models/src/payment_attempt.rs +++ b/crates/diesel_models/src/payment_attempt.rs @@ -101,6 +101,7 @@ pub struct PaymentAttempt { pub capture_before: Option, pub card_discovery: Option, pub charges: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -316,6 +317,7 @@ pub struct PaymentAttemptNew { pub extended_authorization_applied: Option, pub capture_before: Option, pub charges: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -835,6 +837,7 @@ pub struct PaymentAttemptUpdateInternal { // customer_acceptance: Option, // card_network: Option, pub connector_token_details: Option, + pub feature_metadata: Option, } #[cfg(feature = "v1")] @@ -3525,6 +3528,27 @@ pub enum RedirectForm { common_utils::impl_to_sql_from_sql_json!(RedirectForm); +#[cfg(feature = "v2")] +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::pg::sql_types::Jsonb)] +pub struct PaymentAttemptFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive( + Clone, Debug, serde::Deserialize, serde::Serialize, Eq, PartialEq, diesel::AsExpression, +)] +#[diesel(sql_type = diesel::pg::sql_types::Jsonb)] +pub struct PaymentAttemptRecoveryData { + pub attempt_triggered_by: common_enums::TriggeredBy, +} + +#[cfg(feature = "v2")] +common_utils::impl_to_sql_from_sql_json!(PaymentAttemptFeatureMetadata); + mod tests { #[test] diff --git a/crates/diesel_models/src/schema_v2.rs b/crates/diesel_models/src/schema_v2.rs index c4db38c0ee7..69ba37e8df6 100644 --- a/crates/diesel_models/src/schema_v2.rs +++ b/crates/diesel_models/src/schema_v2.rs @@ -883,6 +883,7 @@ diesel::table! { capture_before -> Nullable, card_discovery -> Nullable, charges -> Nullable, + feature_metadata -> Nullable, } } diff --git a/crates/hyperswitch_domain_models/Cargo.toml b/crates/hyperswitch_domain_models/Cargo.toml index ced2151e52b..3de5358a2bd 100644 --- a/crates/hyperswitch_domain_models/Cargo.toml +++ b/crates/hyperswitch_domain_models/Cargo.toml @@ -17,6 +17,7 @@ v2 = ["api_models/v2", "diesel_models/v2", "common_utils/v2"] v1 = ["api_models/v1", "diesel_models/v1", "common_utils/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2"] +revenue_recovery= [] [dependencies] # First party deps diff --git a/crates/hyperswitch_domain_models/src/lib.rs b/crates/hyperswitch_domain_models/src/lib.rs index 3a1bd933b51..ef2cbe064d6 100644 --- a/crates/hyperswitch_domain_models/src/lib.rs +++ b/crates/hyperswitch_domain_models/src/lib.rs @@ -20,6 +20,8 @@ pub mod payments; pub mod payouts; pub mod refunds; pub mod relay; +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +pub mod revenue_recovery; pub mod router_data; pub mod router_data_v2; pub mod router_flow_types; diff --git a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs index dfce21f28b6..f512711987b 100644 --- a/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs +++ b/crates/hyperswitch_domain_models/src/payments/payment_attempt.rs @@ -20,6 +20,11 @@ use diesel_models::{ PaymentAttemptNew as DieselPaymentAttemptNew, PaymentAttemptUpdate as DieselPaymentAttemptUpdate, }; +#[cfg(feature = "v2")] +use diesel_models::{ + PaymentAttemptFeatureMetadata as DieselPaymentAttemptFeatureMetadata, + PaymentAttemptRecoveryData as DieselPassiveChurnRecoveryData, +}; use error_stack::ResultExt; #[cfg(feature = "v2")] use masking::PeekInterface; @@ -435,6 +440,8 @@ pub struct PaymentAttempt { pub card_discovery: Option, /// Split payment data pub charges: Option, + /// Additional data that might be required by hyperswitch, to enable some specific features. + pub feature_metadata: Option, } impl PaymentAttempt { @@ -558,6 +565,7 @@ impl PaymentAttempt { consts::CONNECTOR_MANDATE_REQUEST_REFERENCE_ID_LENGTH, )), }), + feature_metadata: None, id, card_discovery: None, }) @@ -1854,6 +1862,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_token_details, card_discovery, charges, + feature_metadata, } = self; let AttemptAmountDetails { @@ -1870,6 +1879,7 @@ impl behaviour::Conversion for PaymentAttempt { .map(ConnectorTransactionId::form_id_and_data) .map(|(txn_id, txn_data)| (Some(txn_id), txn_data)) .unwrap_or((None, None)); + let feature_metadata = feature_metadata.as_ref().map(From::from); Ok(DieselPaymentAttempt { payment_id, @@ -1935,6 +1945,7 @@ impl behaviour::Conversion for PaymentAttempt { extended_authorization_applied: None, capture_before: None, charges, + feature_metadata, }) } @@ -2047,6 +2058,7 @@ impl behaviour::Conversion for PaymentAttempt { payment_method_billing_address, connector_token_details: storage_model.connector_token_details, card_discovery: storage_model.card_discovery, + feature_metadata: storage_model.feature_metadata.map(From::from), }) } .await @@ -2102,6 +2114,7 @@ impl behaviour::Conversion for PaymentAttempt { connector_token_details, card_discovery, charges, + feature_metadata, } = self; let card_network = payment_method_data @@ -2177,6 +2190,7 @@ impl behaviour::Conversion for PaymentAttempt { extended_authorization_applied: None, request_extended_authorization: None, capture_before: None, + feature_metadata: feature_metadata.as_ref().map(From::from), }) } } @@ -2210,6 +2224,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: Some(authentication_type), + feature_metadata: None, }, PaymentAttemptUpdate::ErrorUpdate { status, @@ -2236,6 +2251,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::ConfirmIntentResponse(confirm_intent_response_update) => { let ConfirmIntentResponseUpdate { @@ -2267,6 +2283,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details, authentication_type: None, + feature_metadata: None, } } PaymentAttemptUpdate::SyncUpdate { @@ -2292,6 +2309,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_to_capture: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::CaptureUpdate { status, @@ -2316,6 +2334,7 @@ impl From for diesel_models::PaymentAttemptUpdateInternal connector_metadata: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, PaymentAttemptUpdate::PreCaptureUpdate { amount_to_capture, @@ -2339,7 +2358,44 @@ impl From for diesel_models::PaymentAttemptUpdateInternal amount_capturable: None, connector_token_details: None, authentication_type: None, + feature_metadata: None, }, } } } +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, PartialEq)] +pub struct PaymentAttemptFeatureMetadata { + pub revenue_recovery: Option, +} + +#[cfg(feature = "v2")] +#[derive(Debug, Clone, serde::Serialize, PartialEq)] +pub struct PaymentAttemptRevenueRecoveryData { + pub attempt_triggered_by: common_enums::TriggeredBy, +} + +#[cfg(feature = "v2")] +impl From<&PaymentAttemptFeatureMetadata> for DieselPaymentAttemptFeatureMetadata { + fn from(item: &PaymentAttemptFeatureMetadata) -> Self { + let revenue_recovery = + item.revenue_recovery + .as_ref() + .map(|recovery_data| DieselPassiveChurnRecoveryData { + attempt_triggered_by: recovery_data.attempt_triggered_by, + }); + Self { revenue_recovery } + } +} + +#[cfg(feature = "v2")] +impl From for PaymentAttemptFeatureMetadata { + fn from(item: DieselPaymentAttemptFeatureMetadata) -> Self { + let revenue_recovery = + item.revenue_recovery + .map(|recovery_data| PaymentAttemptRevenueRecoveryData { + attempt_triggered_by: recovery_data.attempt_triggered_by, + }); + Self { revenue_recovery } + } +} diff --git a/crates/hyperswitch_domain_models/src/revenue_recovery.rs b/crates/hyperswitch_domain_models/src/revenue_recovery.rs new file mode 100644 index 00000000000..8467189d27a --- /dev/null +++ b/crates/hyperswitch_domain_models/src/revenue_recovery.rs @@ -0,0 +1,190 @@ +use api_models::webhooks; +use time::PrimitiveDateTime; + +/// Recovery payload is unified struct constructed from billing connectors +#[derive(Debug)] +pub struct RevenueRecoveryAttemptData { + /// transaction amount against invoice, accepted in minor unit. + pub amount: common_utils::types::MinorUnit, + /// currency of the transaction + pub currency: common_enums::enums::Currency, + /// merchant reference id at billing connector. ex: invoice_id + pub merchant_reference_id: common_utils::id_type::PaymentReferenceId, + /// transaction id reference at payment connector + pub connector_transaction_id: Option, + /// error code sent by billing connector. + pub error_code: Option, + /// error message sent by billing connector. + pub error_message: Option, + /// mandate token at payment processor end. + pub processor_payment_method_token: Option, + /// customer id at payment connector for which mandate is attached. + pub connector_customer_id: Option, + /// Payment gateway identifier id at billing processor. + pub connector_account_reference_id: Option, + /// timestamp at which transaction has been created at billing connector + pub transaction_created_at: Option, + /// transaction status at billing connector equivalent to payment attempt status. + pub status: common_enums::enums::AttemptStatus, + /// payment method of payment attempt. + pub payment_method_type: common_enums::enums::PaymentMethod, + /// payment method sub type of the payment attempt. + pub payment_method_sub_type: common_enums::enums::PaymentMethodType, +} + +/// This is unified struct for Revenue Recovery Invoice Data and it is constructed from billing connectors +#[derive(Debug)] +pub struct RevenueRecoveryInvoiceData { + /// invoice amount at billing connector + pub amount: common_utils::types::MinorUnit, + /// currency of the amount. + pub currency: common_enums::enums::Currency, + /// merchant reference id at billing connector. ex: invoice_id + pub merchant_reference_id: common_utils::id_type::PaymentReferenceId, +} + +/// type of action that needs to taken after consuming recovery payload +#[derive(Debug)] +pub enum RecoveryAction { + /// Stops the process tracker and update the payment intent. + CancelInvoice, + /// Records the external transaction against payment intent. + ScheduleFailedPayment, + /// Records the external payment and stops the internal process tracker. + SuccessPaymentExternal, + /// Pending payments from billing processor. + PendingPayment, + /// No action required. + NoAction, + /// Invalid event has been received. + InvalidAction, +} + +pub struct RecoveryPaymentIntent { + pub payment_id: common_utils::id_type::GlobalPaymentId, + pub status: common_enums::enums::IntentStatus, + pub feature_metadata: Option, +} + +pub struct RecoveryPaymentAttempt { + pub attempt_id: common_utils::id_type::GlobalAttemptId, + pub attempt_status: common_enums::AttemptStatus, + pub feature_metadata: Option, +} + +impl RecoveryPaymentAttempt { + pub fn get_attempt_triggered_by(self) -> Option { + self.feature_metadata.and_then(|metadata| { + metadata + .revenue_recovery + .map(|recovery| recovery.attempt_triggered_by) + }) + } +} + +impl RecoveryAction { + pub fn get_action( + event_type: webhooks::IncomingWebhookEvent, + attempt_triggered_by: Option, + ) -> Self { + match event_type { + webhooks::IncomingWebhookEvent::PaymentIntentFailure + | webhooks::IncomingWebhookEvent::PaymentIntentSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentProcessing + | webhooks::IncomingWebhookEvent::PaymentIntentPartiallyFunded + | webhooks::IncomingWebhookEvent::PaymentIntentCancelled + | webhooks::IncomingWebhookEvent::PaymentIntentCancelFailure + | webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentAuthorizationFailure + | webhooks::IncomingWebhookEvent::PaymentIntentCaptureSuccess + | webhooks::IncomingWebhookEvent::PaymentIntentCaptureFailure + | webhooks::IncomingWebhookEvent::PaymentActionRequired + | webhooks::IncomingWebhookEvent::EventNotSupported + | webhooks::IncomingWebhookEvent::SourceChargeable + | webhooks::IncomingWebhookEvent::SourceTransactionCreated + | webhooks::IncomingWebhookEvent::RefundFailure + | webhooks::IncomingWebhookEvent::RefundSuccess + | webhooks::IncomingWebhookEvent::DisputeOpened + | webhooks::IncomingWebhookEvent::DisputeExpired + | webhooks::IncomingWebhookEvent::DisputeAccepted + | webhooks::IncomingWebhookEvent::DisputeCancelled + | webhooks::IncomingWebhookEvent::DisputeChallenged + | webhooks::IncomingWebhookEvent::DisputeWon + | webhooks::IncomingWebhookEvent::DisputeLost + | webhooks::IncomingWebhookEvent::MandateActive + | webhooks::IncomingWebhookEvent::MandateRevoked + | webhooks::IncomingWebhookEvent::EndpointVerification + | webhooks::IncomingWebhookEvent::ExternalAuthenticationARes + | webhooks::IncomingWebhookEvent::FrmApproved + | webhooks::IncomingWebhookEvent::FrmRejected + | webhooks::IncomingWebhookEvent::PayoutSuccess + | webhooks::IncomingWebhookEvent::PayoutFailure + | webhooks::IncomingWebhookEvent::PayoutProcessing + | webhooks::IncomingWebhookEvent::PayoutCancelled + | webhooks::IncomingWebhookEvent::PayoutCreated + | webhooks::IncomingWebhookEvent::PayoutExpired + | webhooks::IncomingWebhookEvent::PayoutReversed => Self::InvalidAction, + webhooks::IncomingWebhookEvent::RecoveryPaymentFailure => match attempt_triggered_by { + Some(common_enums::TriggeredBy::Internal) => Self::NoAction, + Some(common_enums::TriggeredBy::External) | None => Self::ScheduleFailedPayment, + }, + webhooks::IncomingWebhookEvent::RecoveryPaymentSuccess => match attempt_triggered_by { + Some(common_enums::TriggeredBy::Internal) => Self::NoAction, + Some(common_enums::TriggeredBy::External) | None => Self::SuccessPaymentExternal, + }, + webhooks::IncomingWebhookEvent::RecoveryPaymentPending => Self::PendingPayment, + webhooks::IncomingWebhookEvent::RecoveryInvoiceCancel => Self::CancelInvoice, + } + } +} + +impl From<&RevenueRecoveryInvoiceData> for api_models::payments::AmountDetails { + fn from(data: &RevenueRecoveryInvoiceData) -> Self { + let amount = api_models::payments::AmountDetailsSetter { + order_amount: data.amount.into(), + currency: data.currency, + shipping_cost: None, + order_tax_amount: None, + skip_external_tax_calculation: common_enums::TaxCalculationOverride::Skip, + skip_surcharge_calculation: common_enums::SurchargeCalculationOverride::Skip, + surcharge_amount: None, + tax_on_surcharge: None, + }; + Self::new(amount) + } +} + +impl From<&RevenueRecoveryInvoiceData> for api_models::payments::PaymentsCreateIntentRequest { + fn from(data: &RevenueRecoveryInvoiceData) -> Self { + let amount_details = api_models::payments::AmountDetails::from(data); + Self { + amount_details, + merchant_reference_id: Some(data.merchant_reference_id.clone()), + routing_algorithm_id: None, + // Payments in the revenue recovery flow are always recurring transactions, + // so capture method will be always automatic. + capture_method: Some(common_enums::CaptureMethod::Automatic), + authentication_type: Some(common_enums::AuthenticationType::NoThreeDs), + billing: None, + shipping: None, + customer_id: None, + customer_present: Some(common_enums::PresenceOfCustomerDuringPayment::Absent), + description: None, + return_url: None, + setup_future_usage: None, + apply_mit_exemption: None, + statement_descriptor: None, + order_details: None, + allowed_payment_method_types: None, + metadata: None, + connector_metadata: None, + feature_metadata: None, + payment_link_enabled: None, + payment_link_config: None, + request_incremental_authorization: None, + session_expiry: None, + frm_metadata: None, + request_external_three_ds_authentication: None, + } + } +} diff --git a/crates/hyperswitch_interfaces/Cargo.toml b/crates/hyperswitch_interfaces/Cargo.toml index 99a06ff2e3a..f0402c45f74 100644 --- a/crates/hyperswitch_interfaces/Cargo.toml +++ b/crates/hyperswitch_interfaces/Cargo.toml @@ -10,8 +10,10 @@ license.workspace = true default = ["dummy_connector", "frm", "payouts"] dummy_connector = [] v1 = ["hyperswitch_domain_models/v1", "api_models/v1", "common_utils/v1"] +v2 = [] payouts = ["hyperswitch_domain_models/payouts"] frm = ["hyperswitch_domain_models/frm"] +revenue_recovery= [] [dependencies] actix-web = "4.5.1" diff --git a/crates/hyperswitch_interfaces/src/webhooks.rs b/crates/hyperswitch_interfaces/src/webhooks.rs index cc3bd487645..6a243cf09f7 100644 --- a/crates/hyperswitch_interfaces/src/webhooks.rs +++ b/crates/hyperswitch_interfaces/src/webhooks.rs @@ -275,4 +275,33 @@ pub trait IncomingWebhook: ConnectorCommon + Sync { > { Ok(None) } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + /// get revenue recovery invoice details + fn get_revenue_recovery_attempt_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryAttemptData, + errors::ConnectorError, + > { + Err(errors::ConnectorError::NotImplemented( + "get_revenue_recovery_attempt_details method".to_string(), + ) + .into()) + } + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + /// get revenue recovery transaction details + fn get_revenue_recovery_invoice_details( + &self, + _request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryInvoiceData, + errors::ConnectorError, + > { + Err(errors::ConnectorError::NotImplemented( + "get_revenue_recovery_invoice_details method".to_string(), + ) + .into()) + } } diff --git a/crates/openapi/src/openapi.rs b/crates/openapi/src/openapi.rs index b75d1d22854..1b6f73fb1cd 100644 --- a/crates/openapi/src/openapi.rs +++ b/crates/openapi/src/openapi.rs @@ -281,6 +281,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::enums::PaymentType, api_models::enums::ScaExemptionType, api_models::enums::PaymentMethod, + api_models::enums::TriggeredBy, api_models::enums::PaymentMethodType, api_models::enums::ConnectorType, api_models::enums::PayoutConnectors, diff --git a/crates/openapi/src/openapi_v2.rs b/crates/openapi/src/openapi_v2.rs index 752bd9707ff..a4eb67b64f4 100644 --- a/crates/openapi/src/openapi_v2.rs +++ b/crates/openapi/src/openapi_v2.rs @@ -316,6 +316,8 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::AddressDetails, api_models::payments::BankDebitData, api_models::payments::AliPayQr, + api_models::payments::PaymentAttemptFeatureMetadata, + api_models::payments::PaymentAttemptRevenueRecoveryData, api_models::payments::AliPayRedirection, api_models::payments::MomoRedirection, api_models::payments::TouchNGoRedirection, @@ -477,6 +479,7 @@ Never share your secret api keys. Keep them guarded and secure. api_models::payments::PaymentRevenueRecoveryMetadata, api_models::payments::BillingConnectorPaymentDetails, api_models::enums::PaymentConnectorTransmission, + api_models::enums::TriggeredBy, api_models::payments::PaymentAttemptResponse, api_models::payments::PaymentAttemptAmountDetails, api_models::payments::CaptureResponse, diff --git a/crates/router/Cargo.toml b/crates/router/Cargo.toml index d590797d642..8197670acf3 100644 --- a/crates/router/Cargo.toml +++ b/crates/router/Cargo.toml @@ -33,11 +33,12 @@ payouts = ["api_models/payouts", "common_enums/payouts", "hyperswitch_connectors payout_retry = ["payouts"] recon = ["email", "api_models/recon"] retry = [] -v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2"] +v2 = ["customer_v2", "payment_methods_v2", "common_default", "api_models/v2", "diesel_models/v2", "hyperswitch_domain_models/v2", "storage_impl/v2", "kgraph_utils/v2", "common_utils/v2", "hyperswitch_connectors/v2","hyperswitch_interfaces/v2"] v1 = ["common_default", "api_models/v1", "diesel_models/v1", "hyperswitch_domain_models/v1", "storage_impl/v1", "hyperswitch_interfaces/v1", "kgraph_utils/v1", "common_utils/v1", "hyperswitch_connectors/v1"] customer_v2 = ["api_models/customer_v2", "diesel_models/customer_v2", "hyperswitch_domain_models/customer_v2", "storage_impl/customer_v2"] payment_methods_v2 = ["api_models/payment_methods_v2", "diesel_models/payment_methods_v2", "hyperswitch_domain_models/payment_methods_v2", "storage_impl/payment_methods_v2", "common_utils/payment_methods_v2"] dynamic_routing = ["external_services/dynamic_routing", "storage_impl/dynamic_routing", "api_models/dynamic_routing"] +revenue_recovery =["api_models/revenue_recovery","hyperswitch_interfaces/revenue_recovery","hyperswitch_domain_models/revenue_recovery"] # Partial Auth # The feature reduces the overhead of the router authenticating the merchant for every request, and trusts on `x-merchant-id` header to be present in the request. diff --git a/crates/router/src/core/errors.rs b/crates/router/src/core/errors.rs index 3dbaa999219..d06ffea581f 100644 --- a/crates/router/src/core/errors.rs +++ b/crates/router/src/core/errors.rs @@ -445,3 +445,20 @@ pub enum NetworkTokenizationError { #[error("Failed while calling Network Token Service API")] ApiError, } + +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +#[derive(Debug, thiserror::Error)] +pub enum RevenueRecoveryError { + #[error("Failed to fetch payment intent")] + PaymentIntentFetchFailed, + #[error("Failed to fetch payment attempt")] + PaymentAttemptFetchFailed, + #[error("Failed to get revenue recovery invoice webhook")] + InvoiceWebhookProcessingFailed, + #[error("Failed to get revenue recovery invoice transaction")] + TransactionWebhookProcessingFailed, + #[error("Failed to create payment intent")] + PaymentIntentCreateFailed, + #[error("Source verification failed for billing connector")] + WebhookAuthenticationFailed, +} diff --git a/crates/router/src/core/payments.rs b/crates/router/src/core/payments.rs index 74ac9dc0f37..1898dd40666 100644 --- a/crates/router/src/core/payments.rs +++ b/crates/router/src/core/payments.rs @@ -1803,7 +1803,7 @@ pub(crate) async fn payments_create_and_confirm_intent( #[cfg(feature = "v2")] #[inline] -fn handle_payments_intent_response( +pub fn handle_payments_intent_response( response: hyperswitch_domain_models::api::ApplicationResponse, ) -> CustomResult { match response { diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 5c77452b677..661bd73b506 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1616,7 +1616,7 @@ where let error = payment_attempt .error - .clone() + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from); let payment_address = self.payment_address; @@ -1704,6 +1704,7 @@ where let error = optional_payment_attempt .and_then(|payment_attempt| payment_attempt.error.clone()) + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from); let attempts = self.attempts.as_ref().map(|attempts| { attempts @@ -2811,7 +2812,7 @@ impl ForeignFrom<(storage::PaymentIntent, Option)> error: pa .as_ref() .and_then(|p| p.error.as_ref()) - .map(|e| api_models::payments::ErrorDetails::foreign_from(e.clone())), + .map(api_models::payments::ErrorDetails::foreign_from), cancellation_reason: pa.as_ref().and_then(|p| p.cancellation_reason.clone()), order_details: None, return_url: pi.return_url, @@ -4245,7 +4246,7 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::PaymentA connector: attempt.connector.clone(), error: attempt .error - .clone() + .as_ref() .map(api_models::payments::ErrorDetails::foreign_from), authentication_type: attempt.authentication_type, created_at: attempt.created_at, @@ -4257,10 +4258,16 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::PaymentA payment_method_type: attempt.payment_method_type, connector_reference_id: attempt.connector_response_reference_id.clone(), payment_method_subtype: attempt.get_payment_method_type(), - connector_payment_id: attempt.get_connector_payment_id().map(ToString::to_string), + connector_payment_id: attempt + .get_connector_payment_id() + .map(|str| common_utils::types::ConnectorTransactionId::from(str.to_owned())), payment_method_id: attempt.payment_method_id.clone(), client_source: attempt.client_source.clone(), client_version: attempt.client_version.clone(), + feature_metadata: attempt + .feature_metadata + .as_ref() + .map(api_models::payments::PaymentAttemptFeatureMetadata::foreign_from), } } } @@ -4285,29 +4292,39 @@ impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::AttemptA } #[cfg(feature = "v2")] -impl ForeignFrom +impl ForeignFrom<&hyperswitch_domain_models::payments::payment_attempt::ErrorDetails> for api_models::payments::ErrorDetails { fn foreign_from( - amount_details: hyperswitch_domain_models::payments::payment_attempt::ErrorDetails, + error_details: &hyperswitch_domain_models::payments::payment_attempt::ErrorDetails, ) -> Self { - let hyperswitch_domain_models::payments::payment_attempt::ErrorDetails { - code, - message, - reason, - unified_code, - unified_message, - } = amount_details; - Self { - code, - message: reason.unwrap_or(message), - unified_code, - unified_message, + code: error_details.code.to_owned(), + message: error_details.message.to_owned(), + unified_code: error_details.unified_code.clone(), + unified_message: error_details.unified_message.clone(), } } } +#[cfg(feature = "v2")] +impl + ForeignFrom< + &hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptFeatureMetadata, + > for api_models::payments::PaymentAttemptFeatureMetadata +{ + fn foreign_from( + feature_metadata: &hyperswitch_domain_models::payments::payment_attempt::PaymentAttemptFeatureMetadata, + ) -> Self { + let revenue_recovery = feature_metadata.revenue_recovery.as_ref().map(|recovery| { + api_models::payments::PaymentAttemptRevenueRecoveryData { + attempt_triggered_by: recovery.attempt_triggered_by, + } + }); + Self { revenue_recovery } + } +} + #[cfg(feature = "v2")] impl ForeignFrom for api_models::payments::AmountDetailsResponse diff --git a/crates/router/src/core/webhooks.rs b/crates/router/src/core/webhooks.rs index 64c5617398b..4a9c4106b9f 100644 --- a/crates/router/src/core/webhooks.rs +++ b/crates/router/src/core/webhooks.rs @@ -4,6 +4,8 @@ mod incoming; mod incoming_v2; #[cfg(feature = "v1")] mod outgoing; +#[cfg(all(feature = "revenue_recovery", feature = "v2"))] +mod recovery_incoming; pub mod types; pub mod utils; #[cfg(feature = "olap")] diff --git a/crates/router/src/core/webhooks/incoming_v2.rs b/crates/router/src/core/webhooks/incoming_v2.rs index 900a455f993..c564b405836 100644 --- a/crates/router/src/core/webhooks/incoming_v2.rs +++ b/crates/router/src/core/webhooks/incoming_v2.rs @@ -15,6 +15,8 @@ use hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails; use router_env::{instrument, tracing, tracing_actix_web::RequestId}; use super::{types, utils, MERCHANT_ID}; +#[cfg(feature = "revenue_recovery")] +use crate::core::webhooks::recovery_incoming; use crate::{ core::{ api_locking, @@ -349,6 +351,24 @@ async fn incoming_webhooks_core( api::WebhookFlow::Payout => todo!(), api::WebhookFlow::Subscription => todo!(), + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + api::WebhookFlow::Recovery => { + Box::pin(recovery_incoming::recovery_incoming_webhook_flow( + state.clone(), + merchant_account, + profile, + key_store, + webhook_details, + source_verified, + &connector, + &request_details, + event_type, + req_state, + )) + .await + .change_context(errors::ApiErrorResponse::WebhookProcessingFailure) + .attach_printable("Failed to process recovery incoming webhook")? + } } } } diff --git a/crates/router/src/core/webhooks/recovery_incoming.rs b/crates/router/src/core/webhooks/recovery_incoming.rs new file mode 100644 index 00000000000..3634acfab97 --- /dev/null +++ b/crates/router/src/core/webhooks/recovery_incoming.rs @@ -0,0 +1,307 @@ +use api_models::webhooks; +use common_utils::ext_traits::AsyncExt; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::revenue_recovery; +use hyperswitch_interfaces::webhooks as interface_webhooks; +use router_env::{instrument, tracing}; + +use crate::{ + core::{ + errors::{self, CustomResult}, + payments::{self, operations}, + }, + routes::{app::ReqState, SessionState}, + services::{self, connector_integration_interface}, + types::{api, domain}, +}; + +#[allow(clippy::too_many_arguments)] +#[instrument(skip_all)] +#[cfg(feature = "revenue_recovery")] +pub async fn recovery_incoming_webhook_flow( + state: SessionState, + merchant_account: domain::MerchantAccount, + business_profile: domain::Profile, + key_store: domain::MerchantKeyStore, + _webhook_details: api::IncomingWebhookDetails, + source_verified: bool, + connector: &connector_integration_interface::ConnectorEnum, + request_details: &hyperswitch_interfaces::webhooks::IncomingWebhookRequestDetails<'_>, + event_type: webhooks::IncomingWebhookEvent, + req_state: ReqState, +) -> CustomResult { + // Source verification is necessary for revenue recovery webhooks flow since We don't have payment intent/attempt object created before in our system. + + common_utils::fp_utils::when(!source_verified, || { + Err(report!( + errors::RevenueRecoveryError::WebhookAuthenticationFailed + )) + })?; + + let invoice_details = RevenueRecoveryInvoice( + interface_webhooks::IncomingWebhook::get_revenue_recovery_invoice_details( + connector, + request_details, + ) + .change_context(errors::RevenueRecoveryError::InvoiceWebhookProcessingFailed) + .attach_printable("Failed while getting revenue recovery invoice details")?, + ); + // Fetch the intent using merchant reference id, if not found create new intent. + let payment_intent = invoice_details + .get_payment_intent( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + ) + .await + .transpose() + .async_unwrap_or_else(|| async { + invoice_details + .create_payment_intent( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + ) + .await + }) + .await?; + + let payment_attempt = match event_type.is_recovery_transaction_event() { + true => { + let invoice_transaction_details = RevenueRecoveryAttempt( + interface_webhooks::IncomingWebhook::get_revenue_recovery_attempt_details( + connector, + request_details, + ) + .change_context(errors::RevenueRecoveryError::TransactionWebhookProcessingFailed)?, + ); + + invoice_transaction_details + .get_payment_attempt( + &state, + &req_state, + &merchant_account, + &business_profile, + &key_store, + payment_intent.payment_id.clone(), + ) + .await? + } + false => None, + }; + + let attempt_triggered_by = payment_attempt + .and_then(revenue_recovery::RecoveryPaymentAttempt::get_attempt_triggered_by); + + let action = revenue_recovery::RecoveryAction::get_action(event_type, attempt_triggered_by); + + match action { + revenue_recovery::RecoveryAction::CancelInvoice => todo!(), + revenue_recovery::RecoveryAction::ScheduleFailedPayment => { + todo!() + } + revenue_recovery::RecoveryAction::SuccessPaymentExternal => { + todo!() + } + revenue_recovery::RecoveryAction::PendingPayment => { + router_env::logger::info!( + "Pending transactions are not consumed by the revenue recovery webhooks" + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + revenue_recovery::RecoveryAction::NoAction => { + router_env::logger::info!( + "No Recovery action is taken place for recovery event : {:?} and attempt triggered_by : {:?} ", event_type.clone(), attempt_triggered_by + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + revenue_recovery::RecoveryAction::InvalidAction => { + router_env::logger::error!( + "Invalid Revenue recovery action state has been received, event : {:?}, triggered_by : {:?}", event_type, attempt_triggered_by + ); + Ok(webhooks::WebhookResponseTracker::NoEffect) + } + } +} + +pub struct RevenueRecoveryInvoice(revenue_recovery::RevenueRecoveryInvoiceData); +pub struct RevenueRecoveryAttempt(revenue_recovery::RevenueRecoveryAttemptData); + +impl RevenueRecoveryInvoice { + async fn get_payment_intent( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult, errors::RevenueRecoveryError> + { + let payment_response = Box::pin(payments::payments_get_intent_using_merchant_reference( + state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + req_state.clone(), + &self.0.merchant_reference_id, + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, + )) + .await; + let response = match payment_response { + Ok(services::ApplicationResponse::JsonWithHeaders((payments_response, _))) => { + let payment_id = payments_response.id.clone(); + let status = payments_response.status; + let feature_metadata = payments_response.feature_metadata; + Ok(Some(revenue_recovery::RecoveryPaymentIntent { + payment_id, + status, + feature_metadata, + })) + } + Err(err) + if matches!( + err.current_context(), + &errors::ApiErrorResponse::PaymentNotFound + ) => + { + Ok(None) + } + Ok(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("Unexpected response from payment intent core"), + error @ Err(_) => { + router_env::logger::error!(?error); + Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("failed to fetch payment intent recovery webhook flow") + } + }?; + Ok(response) + } + async fn create_payment_intent( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + ) -> CustomResult { + let payload = api_models::payments::PaymentsCreateIntentRequest::from(&self.0); + let global_payment_id = + common_utils::id_type::GlobalPaymentId::generate(&state.conf.cell_information.id); + + let create_intent_response = Box::pin(payments::payments_intent_core::< + hyperswitch_domain_models::router_flow_types::payments::PaymentCreateIntent, + api_models::payments::PaymentsIntentResponse, + _, + _, + hyperswitch_domain_models::payments::PaymentIntentData< + hyperswitch_domain_models::router_flow_types::payments::PaymentCreateIntent, + >, + >( + state.clone(), + req_state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + payments::operations::PaymentIntentCreate, + payload, + global_payment_id, + hyperswitch_domain_models::payments::HeaderPayload::default(), + None, + )) + .await + .change_context(errors::RevenueRecoveryError::PaymentIntentCreateFailed)?; + let response = payments::handle_payments_intent_response(create_intent_response) + .change_context(errors::RevenueRecoveryError::PaymentIntentCreateFailed)?; + + Ok(revenue_recovery::RecoveryPaymentIntent { + payment_id: response.id, + status: response.status, + feature_metadata: response.feature_metadata, + }) + } +} + +impl RevenueRecoveryAttempt { + async fn get_payment_attempt( + &self, + state: &SessionState, + req_state: &ReqState, + merchant_account: &domain::MerchantAccount, + profile: &domain::Profile, + key_store: &domain::MerchantKeyStore, + payment_id: common_utils::id_type::GlobalPaymentId, + ) -> CustomResult, errors::RevenueRecoveryError> + { + let attempt_response = Box::pin(payments::payments_core::< + hyperswitch_domain_models::router_flow_types::payments::PSync, + api_models::payments::PaymentsRetrieveResponse, + _, + _, + _, + hyperswitch_domain_models::payments::PaymentStatusData< + hyperswitch_domain_models::router_flow_types::payments::PSync, + >, + >( + state.clone(), + req_state.clone(), + merchant_account.clone(), + profile.clone(), + key_store.clone(), + payments::operations::PaymentGet, + api_models::payments::PaymentsRetrieveRequest { + force_sync: false, + expand_attempts: true, + param: None, + }, + payment_id.clone(), + payments::CallConnectorAction::Avoid, + hyperswitch_domain_models::payments::HeaderPayload::default(), + )) + .await; + let response = match attempt_response { + Ok(services::ApplicationResponse::JsonWithHeaders((payments_response, _))) => { + let final_attempt = + self.0 + .connector_transaction_id + .as_ref() + .and_then(|transaction_id| { + payments_response + .find_attempt_in_attempts_list_using_connector_transaction_id( + transaction_id, + ) + }); + let payment_attempt = + final_attempt.map(|attempt_res| revenue_recovery::RecoveryPaymentAttempt { + attempt_id: attempt_res.id.to_owned(), + attempt_status: attempt_res.status.to_owned(), + feature_metadata: attempt_res.feature_metadata.to_owned(), + }); + Ok(payment_attempt) + } + Ok(_) => Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("Unexpected response from payment intent core"), + error @ Err(_) => { + router_env::logger::error!(?error); + Err(errors::RevenueRecoveryError::PaymentIntentFetchFailed) + .attach_printable("failed to fetch payment intent recovery webhook flow") + } + }?; + Ok(response) + } + async fn record_payment_attempt( + &self, + _state: &SessionState, + _req_state: &ReqState, + _merchant_account: &domain::MerchantAccount, + _profile: &domain::Profile, + _key_store: &domain::MerchantKeyStore, + _payment_id: common_utils::id_type::GlobalPaymentId, + ) -> CustomResult { + todo!() + } +} diff --git a/crates/router/src/routes.rs b/crates/router/src/routes.rs index b589d4755f8..0b506611665 100644 --- a/crates/router/src/routes.rs +++ b/crates/router/src/routes.rs @@ -60,6 +60,9 @@ pub mod verify_connector; pub mod webhook_events; pub mod webhooks; +#[cfg(all(feature = "v2", feature = "revenue_recovery"))] +pub mod recovery_webhooks; + pub mod relay; #[cfg(feature = "dummy_connector")] diff --git a/crates/router/src/routes/app.rs b/crates/router/src/routes/app.rs index b8acc66d522..760afc26b0d 100644 --- a/crates/router/src/routes/app.rs +++ b/crates/router/src/routes/app.rs @@ -46,6 +46,8 @@ use super::payouts::*; use super::pm_auth; #[cfg(feature = "oltp")] use super::poll; +#[cfg(all(feature = "v2", feature = "revenue_recovery", feature = "oltp"))] +use super::recovery_webhooks::*; #[cfg(feature = "olap")] use super::routing; #[cfg(all(feature = "olap", feature = "v1"))] @@ -1642,6 +1644,16 @@ impl Webhooks { ), ); + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + { + route = route.service( + web::resource("/recovery/{merchant_id}/{profile_id}/{connector_id}").route( + web::post() + .to(recovery_receive_incoming_webhook::), + ), + ); + } + route } } diff --git a/crates/router/src/routes/lock_utils.rs b/crates/router/src/routes/lock_utils.rs index a50a27b9ec4..f48a7970fde 100644 --- a/crates/router/src/routes/lock_utils.rs +++ b/crates/router/src/routes/lock_utils.rs @@ -178,7 +178,8 @@ impl From for ApiIdentifier { | Flow::IncomingRelayWebhookReceive | Flow::WebhookEventInitialDeliveryAttemptList | Flow::WebhookEventDeliveryAttemptList - | Flow::WebhookEventDeliveryRetry => Self::Webhooks, + | Flow::WebhookEventDeliveryRetry + | Flow::RecoveryIncomingWebhookReceive => Self::Webhooks, Flow::ApiKeyCreate | Flow::ApiKeyRetrieve diff --git a/crates/router/src/routes/recovery_webhooks.rs b/crates/router/src/routes/recovery_webhooks.rs new file mode 100644 index 00000000000..0c83ba1b395 --- /dev/null +++ b/crates/router/src/routes/recovery_webhooks.rs @@ -0,0 +1,53 @@ +use actix_web::{web, HttpRequest, Responder}; +use router_env::{instrument, tracing, Flow}; + +use super::app::AppState; +use crate::{ + core::{ + api_locking, + webhooks::{self, types}, + }, + services::{api, authentication as auth}, +}; + +#[instrument(skip_all, fields(flow = ?Flow::IncomingWebhookReceive))] +pub async fn recovery_receive_incoming_webhook( + state: web::Data, + req: HttpRequest, + body: web::Bytes, + path: web::Path<( + common_utils::id_type::MerchantId, + common_utils::id_type::ProfileId, + common_utils::id_type::MerchantConnectorAccountId, + )>, +) -> impl Responder { + let flow = Flow::RecoveryIncomingWebhookReceive; + let (merchant_id, profile_id, connector_id) = path.into_inner(); + + Box::pin(api::server_wrap( + flow.clone(), + state, + &req, + (), + |state, auth, _, req_state| { + webhooks::incoming_webhooks_wrapper::( + &flow, + state.to_owned(), + req_state, + &req, + auth.merchant_account, + auth.profile, + auth.key_store, + &connector_id, + body.clone(), + false, + ) + }, + &auth::MerchantIdAndProfileIdAuth { + merchant_id, + profile_id, + }, + api_locking::LockAction::NotApplicable, + )) + .await +} diff --git a/crates/router/src/services/connector_integration_interface.rs b/crates/router/src/services/connector_integration_interface.rs index 5903c8e9cc0..d9b04f57344 100644 --- a/crates/router/src/services/connector_integration_interface.rs +++ b/crates/router/src/services/connector_integration_interface.rs @@ -338,6 +338,34 @@ impl api::IncomingWebhook for ConnectorEnum { Self::New(connector) => connector.get_network_txn_id(request), } } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_attempt_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryAttemptData, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_revenue_recovery_attempt_details(request), + Self::New(connector) => connector.get_revenue_recovery_attempt_details(request), + } + } + + #[cfg(all(feature = "revenue_recovery", feature = "v2"))] + fn get_revenue_recovery_invoice_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + hyperswitch_domain_models::revenue_recovery::RevenueRecoveryInvoiceData, + errors::ConnectorError, + > { + match self { + Self::Old(connector) => connector.get_revenue_recovery_invoice_details(request), + Self::New(connector) => connector.get_revenue_recovery_invoice_details(request), + } + } } impl api::ConnectorTransactionId for ConnectorEnum { diff --git a/crates/router_env/src/logger/types.rs b/crates/router_env/src/logger/types.rs index 3eda63bdbaa..6021ee70d9f 100644 --- a/crates/router_env/src/logger/types.rs +++ b/crates/router_env/src/logger/types.rs @@ -267,6 +267,8 @@ pub enum Flow { ToggleBlocklistGuard, /// Incoming Webhook Receive IncomingWebhookReceive, + /// Recovery incoming webhook receive + RecoveryIncomingWebhookReceive, /// Validate payment method flow ValidatePaymentMethod, /// API Key create flow diff --git a/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql new file mode 100644 index 00000000000..ec6a51ca794 --- /dev/null +++ b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/down.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_attempt +DROP COLUMN IF EXISTS feature_metadata; \ No newline at end of file diff --git a/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql new file mode 100644 index 00000000000..bf06a98f7a3 --- /dev/null +++ b/v2_migrations/2025-01-17-042122_add_feature_metadata_in_payment_attempt/up.sql @@ -0,0 +1,2 @@ +ALTER TABLE payment_attempt +ADD COLUMN feature_metadata JSONB; \ No newline at end of file From 9f334c1ebcf0e8ee15ae2af608fb8f27fab2a6b2 Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 24 Feb 2025 00:29:14 +0000 Subject: [PATCH 133/133] chore(version): 2025.02.24.0 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f85a7d2ff42..7351e838c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,30 @@ All notable changes to HyperSwitch will be documented here. - - - +## 2025.02.24.0 + +### Features + +- **connector:** + - Add Samsung pay mandate support for Cybersource ([#7298](https://github.com/juspay/hyperswitch/pull/7298)) ([`9bc8fd4`](https://github.com/juspay/hyperswitch/commit/9bc8fd4d8c4be1b54050398dfb3b574e924e4b5f)) + - Add support for passive churn recovery webhooks ([#7109](https://github.com/juspay/hyperswitch/pull/7109)) ([`0688972`](https://github.com/juspay/hyperswitch/commit/0688972814cf03edbff4bf125a59c338a7e49593)) +- **router:** Add `merchant_configuration_id` in netcetera metadata and make other merchant configurations optional ([#7347](https://github.com/juspay/hyperswitch/pull/7347)) ([`57ab869`](https://github.com/juspay/hyperswitch/commit/57ab8693e26a54cd5fe73b28459c9b03235e5d5b)) +- **samsung_pay:** Collect customer address details form Samsung Pay based on business profile config and connector required fields ([#7320](https://github.com/juspay/hyperswitch/pull/7320)) ([`c14519e`](https://github.com/juspay/hyperswitch/commit/c14519ebd958abc79879244f8180686b2be30d31)) + +### Bug Fixes + +- **connector:** [DATATRANS] Fix Force Sync Flow ([#7331](https://github.com/juspay/hyperswitch/pull/7331)) ([`0e96e24`](https://github.com/juspay/hyperswitch/commit/0e96e2470c6906fe77b35c14821e3ebcfa454e39)) +- **routing:** Fixed 5xx error logs in dynamic routing metrics ([#7335](https://github.com/juspay/hyperswitch/pull/7335)) ([`049fcdb`](https://github.com/juspay/hyperswitch/commit/049fcdb3fb19c6af45392a5ef3a0cbf642598af8)) +- **samsung_pay:** Add payment_method_type duplication check ([#7337](https://github.com/juspay/hyperswitch/pull/7337)) ([`11ff437`](https://github.com/juspay/hyperswitch/commit/11ff437456f9d97205ce07e4d4df63006f3ad0c6)) + +### Miscellaneous Tasks + +- **masking:** Add peek_mut to PeekInterface ([#7281](https://github.com/juspay/hyperswitch/pull/7281)) ([`890a265`](https://github.com/juspay/hyperswitch/commit/890a265e7b1b06aeea100994efbebe3ce898a125)) + +**Full Changelog:** [`2025.02.21.0...2025.02.24.0`](https://github.com/juspay/hyperswitch/compare/2025.02.21.0...2025.02.24.0) + +- - - + ## 2025.02.21.0 ### Features