diff --git a/CHANGELOG.md b/CHANGELOG.md index 8010277b5659..ebd711b2e605 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,39 @@ All notable changes to HyperSwitch will be documented here. - - - +## 1.105.0 (2023-12-23) + +### Features + +- **connector:** [BOA/CYBERSOURCE] Populate connector_transaction_id ([#3202](https://github.com/juspay/hyperswitch/pull/3202)) ([`110d3d2`](https://github.com/juspay/hyperswitch/commit/110d3d211be2edf47533cc5297ae159cad0e5034)) + +**Full Changelog:** [`v1.104.0...v1.105.0`](https://github.com/juspay/hyperswitch/compare/v1.104.0...v1.105.0) + +- - - + + +## 1.104.0 (2023-12-22) + +### Features + +- **connector:** [BOA] Implement apple pay manual flow ([#3191](https://github.com/juspay/hyperswitch/pull/3191)) ([`25fd3d5`](https://github.com/juspay/hyperswitch/commit/25fd3d502e48f10dd3acbdc88caea4007310d4ee)) +- **router:** Make the billing country for apple pay as optional field ([#3188](https://github.com/juspay/hyperswitch/pull/3188)) ([`15987cc`](https://github.com/juspay/hyperswitch/commit/15987cc81ecba3c1d0de4fa0a12424066a8842eb)) + +### Bug Fixes + +- **connector:** + - [Trustpay] Use `connector_request_reference_id` for merchant reference instead of `payment_id` ([#2885](https://github.com/juspay/hyperswitch/pull/2885)) ([`c51c761`](https://github.com/juspay/hyperswitch/commit/c51c761677e8c5ff80de40f8796f340cf1331f96)) + - [BOA/Cyb] Truncate state length to <20 ([#3198](https://github.com/juspay/hyperswitch/pull/3198)) ([`79a18e2`](https://github.com/juspay/hyperswitch/commit/79a18e2bf7bb1f338cf982fb1a152add2ed4e087)) + - [Iatapay] fix error response handling when payment is failed ([#3197](https://github.com/juspay/hyperswitch/pull/3197)) ([`716a74c`](https://github.com/juspay/hyperswitch/commit/716a74cf8449583541c426a5c427c9e32f5b2528)) + - [BOA] Display 2XX Failure Errors ([#3200](https://github.com/juspay/hyperswitch/pull/3200)) ([`07fd9be`](https://github.com/juspay/hyperswitch/commit/07fd9bedf02a1d70fc248fbbab480a5e24a7f077)) + - [CYBERSOURCE] Display 2XX Failure Errors ([#3201](https://github.com/juspay/hyperswitch/pull/3201)) ([`86c2622`](https://github.com/juspay/hyperswitch/commit/86c26221357e14b585f44c6ebe46962c085f6552)) +- **users:** Wrong `user_role` insertion in `invite_user` for new users ([#3193](https://github.com/juspay/hyperswitch/pull/3193)) ([`b06a8d6`](https://github.com/juspay/hyperswitch/commit/b06a8d6e0d7fc4fb1bec30f702d64f0bd5e1068e)) + +**Full Changelog:** [`v1.103.1...v1.104.0`](https://github.com/juspay/hyperswitch/compare/v1.103.1...v1.104.0) + +- - - + + ## 1.103.1 (2023-12-21) ### Bug Fixes diff --git a/config/config.example.toml b/config/config.example.toml index 7b4380ba2db0..f9ae71d3b9ee 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -357,6 +357,7 @@ stripe = { payment_method = "bank_transfer" } nuvei = { payment_method = "card" } shift4 = { payment_method = "card" } bluesnap = { payment_method = "card" } +nmi = {payment_method = "card"} [dummy_connector] enabled = true # Whether dummy connector is enabled or not diff --git a/config/development.toml b/config/development.toml index b6a0f9f99cd9..d365abc46744 100644 --- a/config/development.toml +++ b/config/development.toml @@ -434,6 +434,7 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +nmi = {payment_method = "card"} [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index eab1ea5408c0..63be7339c7bc 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -247,6 +247,7 @@ stripe = {payment_method = "bank_transfer"} nuvei = {payment_method = "card"} shift4 = {payment_method = "card"} bluesnap = {payment_method = "card"} +nmi = {payment_method = "card"} [dummy_connector] enabled = true diff --git a/crates/api_models/src/user_role.rs b/crates/api_models/src/user_role.rs index 735cd240b6e7..72fca2b2f084 100644 --- a/crates/api_models/src/user_role.rs +++ b/crates/api_models/src/user_role.rs @@ -32,6 +32,8 @@ pub enum Permission { DisputeWrite, MandateRead, MandateWrite, + CustomerRead, + CustomerWrite, FileRead, FileWrite, Analytics, @@ -53,6 +55,7 @@ pub enum PermissionModule { Routing, Analytics, Mandates, + Customer, Disputes, Files, ThreeDsDecisionManager, diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index 83a34b87dd0b..c90af883d6e3 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -1450,7 +1450,25 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserCardCvc, value: None, } - ) + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "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: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), ] ), common: HashMap::new(), @@ -3481,7 +3499,25 @@ impl Default for super::settings::RequiredFields { field_type: enums::FieldType::UserCardCvc, value: None, } - ) + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "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: "billing.address.last_name".to_string(), + display_name: "billing_last_name".to_string(), + field_type: enums::FieldType::UserBillingName, + value: None, + } + ), ] ), common: HashMap::new(), diff --git a/crates/router/src/connector/bankofamerica/transformers.rs b/crates/router/src/connector/bankofamerica/transformers.rs index deca1dc6744d..477bbec7350b 100644 --- a/crates/router/src/connector/bankofamerica/transformers.rs +++ b/crates/router/src/connector/bankofamerica/transformers.rs @@ -1,7 +1,7 @@ use api_models::payments; use base64::Engine; use common_utils::pii; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ @@ -111,6 +111,19 @@ pub struct GooglePayPaymentInformation { fluid_data: FluidData, } +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenizedCard { + transaction_type: TransactionType, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct ApplePayTokenPaymentInformation { + fluid_data: FluidData, + tokenized_card: ApplePayTokenizedCard, +} + #[derive(Debug, Serialize)] #[serde(rename_all = "camelCase")] pub struct ApplePayPaymentInformation { @@ -123,6 +136,7 @@ pub enum PaymentInformation { Cards(CardPaymentInformation), GooglePay(GooglePayPaymentInformation), ApplePay(ApplePayPaymentInformation), + ApplePayToken(ApplePayTokenPaymentInformation), } #[derive(Debug, Serialize)] @@ -188,12 +202,14 @@ fn build_bill_to( .address .as_ref() .ok_or_else(utils::missing_field_err("billing.address"))?; + let mut state = address.to_state_code()?.peek().clone(); + state.truncate(20); Ok(BillTo { first_name: address.get_first_name()?.to_owned(), last_name: address.get_last_name()?.to_owned(), address1: address.get_line1()?.to_owned(), locality: address.get_city()?.to_owned(), - administrative_area: address.to_state_code()?, + administrative_area: Secret::from(state), postal_code: address.get_zip()?.to_owned(), country: address.get_country()?.to_owned(), email, @@ -293,7 +309,7 @@ impl From<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } -#[derive(Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ClientReferenceInformation { code: Option, @@ -434,14 +450,42 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> match item.router_data.request.payment_method_data.clone() { payments::PaymentMethodData::Card(ccard) => Self::try_from((item, ccard)), payments::PaymentMethodData::Wallet(wallet_data) => match wallet_data { - payments::WalletData::ApplePay(_) => { - let payment_method_token = item.router_data.get_payment_method_token()?; - match payment_method_token { - types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - Self::try_from((item, decrypt_data)) - } - types::PaymentMethodToken::Token(_) => { - Err(errors::ConnectorError::InvalidWalletToken)? + payments::WalletData::ApplePay(apple_pay_data) => { + match item.router_data.payment_method_token.clone() { + Some(payment_method_token) => match payment_method_token { + types::PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + Self::try_from((item, decrypt_data)) + } + types::PaymentMethodToken::Token(_) => { + Err(errors::ConnectorError::InvalidWalletToken)? + } + }, + None => { + let email = item.router_data.request.get_email()?; + let bill_to = build_bill_to(item.router_data.get_billing()?, email)?; + let order_information = OrderInformationWithBill::from((item, bill_to)); + let processing_information = ProcessingInformation::from(( + item, + Some(PaymentSolution::ApplePay), + )); + let client_reference_information = + ClientReferenceInformation::from(item); + let payment_information = PaymentInformation::ApplePayToken( + ApplePayTokenPaymentInformation { + fluid_data: FluidData { + value: Secret::from(apple_pay_data.payment_data), + }, + tokenized_card: ApplePayTokenizedCard { + transaction_type: TransactionType::ApplePay, + }, + }, + ); + Ok(Self { + processing_information, + payment_information, + order_information, + client_reference_information, + }) } } } @@ -497,7 +541,7 @@ impl TryFrom<&BankOfAmericaRouterData<&types::PaymentsAuthorizeRouterData>> } } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum BankofamericaPaymentStatus { Authorized, @@ -507,8 +551,16 @@ pub enum BankofamericaPaymentStatus { Reversed, Pending, Declined, + Rejected, + Challenge, AuthorizedPendingReview, + AuthorizedRiskDeclined, Transmitted, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + //PartialAuthorized, not being consumed yet. } impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { @@ -536,8 +588,15 @@ impl ForeignFrom<(BankofamericaPaymentStatus, bool)> for enums::AttemptStatus { BankofamericaPaymentStatus::Voided | BankofamericaPaymentStatus::Reversed => { Self::Voided } - BankofamericaPaymentStatus::Failed | BankofamericaPaymentStatus::Declined => { - Self::Failure + BankofamericaPaymentStatus::Failed + | BankofamericaPaymentStatus::Declined + | BankofamericaPaymentStatus::AuthorizedRiskDeclined + | BankofamericaPaymentStatus::InvalidRequest + | BankofamericaPaymentStatus::Rejected + | BankofamericaPaymentStatus::ServerError => Self::Failure, + BankofamericaPaymentStatus::PendingAuthentication => Self::AuthenticationPending, + BankofamericaPaymentStatus::PendingReview | BankofamericaPaymentStatus::Challenge => { + Self::Pending } } } @@ -550,12 +609,13 @@ pub enum BankOfAmericaPaymentsResponse { ErrorInformation(BankOfAmericaErrorInformationResponse), } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct BankOfAmericaClientReferenceResponse { id: String, status: BankofamericaPaymentStatus, client_reference_information: ClientReferenceInformation, + error_information: Option, } #[derive(Debug, Deserialize)] @@ -565,12 +625,101 @@ pub struct BankOfAmericaErrorInformationResponse { error_information: BankOfAmericaErrorInformation, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] pub struct BankOfAmericaErrorInformation { reason: Option, message: Option, } +fn get_error_response_if_failure( + (info_response, status, http_code): ( + &BankOfAmericaClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Option { + if is_payment_failure(status) { + let (message, reason) = match info_response.error_information.as_ref() { + Some(error_info) => ( + error_info + .message + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + error_info.reason.clone(), + ), + None => (consts::NO_ERROR_MESSAGE.to_string(), None), + }; + + Some(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message, + reason, + status_code: http_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(info_response.id.clone()), + }) + } else { + None + } +} + +fn is_payment_failure(status: enums::AttemptStatus) -> bool { + match status { + common_enums::AttemptStatus::AuthenticationFailed + | common_enums::AttemptStatus::AuthorizationFailed + | common_enums::AttemptStatus::CaptureFailed + | common_enums::AttemptStatus::VoidFailed + | common_enums::AttemptStatus::Failure => true, + common_enums::AttemptStatus::Started + | common_enums::AttemptStatus::RouterDeclined + | common_enums::AttemptStatus::AuthenticationPending + | common_enums::AttemptStatus::AuthenticationSuccessful + | common_enums::AttemptStatus::Authorized + | common_enums::AttemptStatus::Charged + | common_enums::AttemptStatus::Authorizing + | common_enums::AttemptStatus::CodInitiated + | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidInitiated + | common_enums::AttemptStatus::CaptureInitiated + | common_enums::AttemptStatus::AutoRefunded + | common_enums::AttemptStatus::PartialCharged + | common_enums::AttemptStatus::PartialChargedAndChargeable + | common_enums::AttemptStatus::Unresolved + | common_enums::AttemptStatus::Pending + | common_enums::AttemptStatus::PaymentMethodAwaited + | common_enums::AttemptStatus::ConfirmationAwaited + | common_enums::AttemptStatus::DeviceDataCollectionPending => false, + } +} + +fn get_payment_response( + (info_response, status, http_code): ( + &BankOfAmericaClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Result { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(error), + None => Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed: None, + }), + } +} + impl TryFrom< types::ResponseRouterData< @@ -591,29 +740,18 @@ impl >, ) -> Result { match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from(( - info_response.status, + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = enums::AttemptStatus::foreign_from(( + info_response.status.clone(), item.data.request.is_auto_capture()?, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - info_response.id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id), - ), - incremental_authorization_allowed: None, - }), - ..item.data - }), + )); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { response: Err(types::ErrorResponse { code: consts::NO_ERROR_CODE.to_string(), @@ -624,7 +762,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(error_response.id), }), status: enums::AttemptStatus::Failure, ..item.data @@ -653,26 +791,16 @@ impl >, ) -> Result { match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from((info_response.status, true)), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - info_response.id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id), - ), - incremental_authorization_allowed: None, - }), - ..item.data - }), + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), true)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { response: Err(types::ErrorResponse { code: consts::NO_ERROR_CODE.to_string(), @@ -683,7 +811,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(error_response.id), }), ..item.data }), @@ -711,26 +839,16 @@ impl >, ) -> Result { match item.response { - BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from((info_response.status, false)), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - info_response.id.clone(), - ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id), - ), - incremental_authorization_allowed: None, - }), - ..item.data - }), + BankOfAmericaPaymentsResponse::ClientReferenceInformation(info_response) => { + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + let response = get_payment_response((&info_response, status, item.http_code)); + Ok(Self { + status, + response, + ..item.data + }) + } BankOfAmericaPaymentsResponse::ErrorInformation(error_response) => Ok(Self { response: Err(types::ErrorResponse { code: consts::NO_ERROR_CODE.to_string(), @@ -741,7 +859,7 @@ impl reason: error_response.error_information.reason, status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(error_response.id), }), ..item.data }), @@ -762,6 +880,7 @@ pub struct BankOfAmericaApplicationInfoResponse { id: String, application_information: ApplicationInformation, client_reference_information: Option, + error_information: Option, } #[derive(Debug, Deserialize)] @@ -790,25 +909,54 @@ impl >, ) -> Result { match item.response { - BankOfAmericaTransactionResponse::ApplicationInformation(app_response) => Ok(Self { - status: enums::AttemptStatus::foreign_from(( + BankOfAmericaTransactionResponse::ApplicationInformation(app_response) => { + let status = enums::AttemptStatus::foreign_from(( app_response.application_information.status, item.data.request.is_auto_capture()?, - )), - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(app_response.id.clone()), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: app_response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(app_response.id)), - incremental_authorization_allowed: None, - }), - ..item.data - }), + )); + if is_payment_failure(status) { + let (message, reason) = match app_response.error_information { + Some(error_info) => ( + error_info + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + error_info.reason, + ), + None => (consts::NO_ERROR_MESSAGE.to_string(), None), + }; + Ok(Self { + response: Err(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message, + reason, + status_code: item.http_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(app_response.id), + }), + status: enums::AttemptStatus::Failure, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + app_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: app_response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(app_response.id)), + incremental_authorization_allowed: None, + }), + ..item.data + }) + } + } BankOfAmericaTransactionResponse::ErrorInformation(error_response) => Ok(Self { status: item.data.status, response: Ok(types::PaymentsResponseData::TransactionResponse { diff --git a/crates/router/src/connector/cybersource/transformers.rs b/crates/router/src/connector/cybersource/transformers.rs index f99c90989d0c..1d828a95d60a 100644 --- a/crates/router/src/connector/cybersource/transformers.rs +++ b/crates/router/src/connector/cybersource/transformers.rs @@ -1,7 +1,7 @@ use api_models::payments; use base64::Engine; use common_utils::pii; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ @@ -427,12 +427,14 @@ fn build_bill_to( .address .as_ref() .ok_or_else(utils::missing_field_err("billing.address"))?; + let mut state = address.to_state_code()?.peek().clone(); + state.truncate(20); Ok(BillTo { first_name: address.get_first_name()?.to_owned(), last_name: address.get_last_name()?.to_owned(), address1: address.get_line1()?.to_owned(), locality: address.get_city()?.to_owned(), - administrative_area: address.to_state_code()?, + administrative_area: Secret::from(state), postal_code: address.get_zip()?.to_owned(), country: address.get_country()?.to_owned(), email, @@ -771,8 +773,16 @@ pub enum CybersourcePaymentStatus { Reversed, Pending, Declined, + Rejected, + Challenge, AuthorizedPendingReview, + AuthorizedRiskDeclined, Transmitted, + InvalidRequest, + ServerError, + PendingAuthentication, + PendingReview, + //PartialAuthorized, not being consumed yet. } #[derive(Debug, Clone, Deserialize)] @@ -806,7 +816,16 @@ impl ForeignFrom<(CybersourcePaymentStatus, bool)> for enums::AttemptStatus { Self::Charged } CybersourcePaymentStatus::Voided | CybersourcePaymentStatus::Reversed => Self::Voided, - CybersourcePaymentStatus::Failed | CybersourcePaymentStatus::Declined => Self::Failure, + CybersourcePaymentStatus::Failed + | CybersourcePaymentStatus::Declined + | CybersourcePaymentStatus::AuthorizedRiskDeclined + | CybersourcePaymentStatus::Rejected + | CybersourcePaymentStatus::InvalidRequest + | CybersourcePaymentStatus::ServerError => Self::Failure, + CybersourcePaymentStatus::PendingAuthentication => Self::AuthenticationPending, + CybersourcePaymentStatus::PendingReview | CybersourcePaymentStatus::Challenge => { + Self::Pending + } } } } @@ -840,13 +859,14 @@ pub enum CybersourcePaymentsResponse { ErrorInformation(CybersourceErrorInformationResponse), } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CybersourceClientReferenceResponse { id: String, status: CybersourcePaymentStatus, client_reference_information: ClientReferenceInformation, token_information: Option, + error_information: Option, } #[derive(Debug, Clone, Deserialize)] @@ -919,13 +939,114 @@ impl reason: error_response.error_information.reason.clone(), status_code: item.http_code, attempt_status: None, - connector_transaction_id: None, + connector_transaction_id: Some(error_response.id.clone()), }), ..item.data } } } +fn get_error_response_if_failure( + (info_response, status, http_code): ( + &CybersourceClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Option { + if is_payment_failure(status) { + let (message, reason) = match info_response.error_information.as_ref() { + Some(error_info) => ( + error_info + .message + .clone() + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + error_info.reason.clone(), + ), + None => (consts::NO_ERROR_MESSAGE.to_string(), None), + }; + + Some(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message, + reason, + status_code: http_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(info_response.id.clone()), + }) + } else { + None + } +} + +fn is_payment_failure(status: enums::AttemptStatus) -> bool { + match status { + common_enums::AttemptStatus::AuthenticationFailed + | common_enums::AttemptStatus::AuthorizationFailed + | common_enums::AttemptStatus::CaptureFailed + | common_enums::AttemptStatus::VoidFailed + | common_enums::AttemptStatus::Failure => true, + common_enums::AttemptStatus::Started + | common_enums::AttemptStatus::RouterDeclined + | common_enums::AttemptStatus::AuthenticationPending + | common_enums::AttemptStatus::AuthenticationSuccessful + | common_enums::AttemptStatus::Authorized + | common_enums::AttemptStatus::Charged + | common_enums::AttemptStatus::Authorizing + | common_enums::AttemptStatus::CodInitiated + | common_enums::AttemptStatus::Voided + | common_enums::AttemptStatus::VoidInitiated + | common_enums::AttemptStatus::CaptureInitiated + | common_enums::AttemptStatus::AutoRefunded + | common_enums::AttemptStatus::PartialCharged + | common_enums::AttemptStatus::PartialChargedAndChargeable + | common_enums::AttemptStatus::Unresolved + | common_enums::AttemptStatus::Pending + | common_enums::AttemptStatus::PaymentMethodAwaited + | common_enums::AttemptStatus::ConfirmationAwaited + | common_enums::AttemptStatus::DeviceDataCollectionPending => false, + } +} + +fn get_payment_response( + (info_response, status, http_code): ( + &CybersourceClientReferenceResponse, + enums::AttemptStatus, + u16, + ), +) -> Result { + let error_response = get_error_response_if_failure((info_response, status, http_code)); + match error_response { + Some(error) => Err(error), + None => { + let incremental_authorization_allowed = + Some(status == enums::AttemptStatus::Authorized); + let mandate_reference = + info_response + .token_information + .clone() + .map(|token_info| types::MandateReference { + connector_mandate_id: Some(token_info.instrument_identifier.id), + payment_method_id: None, + }); + Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(info_response.id.clone()), + redirection_data: None, + mandate_reference, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + info_response + .client_reference_information + .code + .clone() + .unwrap_or(info_response.id.clone()), + ), + incremental_authorization_allowed, + }) + } + } +} + impl TryFrom< types::ResponseRouterData< @@ -948,35 +1069,13 @@ impl match item.response { CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { let status = enums::AttemptStatus::foreign_from(( - info_response.status, + info_response.status.clone(), item.data.request.is_auto_capture()?, )); - let incremental_authorization_allowed = - Some(status == enums::AttemptStatus::Authorized); - let mandate_reference = - info_response - .token_information - .map(|token_info| types::MandateReference { - connector_mandate_id: Some(token_info.instrument_identifier.id), - payment_method_id: None, - }); - let connector_response_reference_id = Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id.clone()), - ); + let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id), - redirection_data: None, - mandate_reference, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id, - incremental_authorization_allowed, - }), + response, ..item.data }) } @@ -1008,24 +1107,12 @@ impl ) -> Result { match item.response { CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { - let status = enums::AttemptStatus::foreign_from((info_response.status, true)); - let connector_response_reference_id = Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id.clone()), - ); + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), true)); + let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id, - incremental_authorization_allowed: None, - }), + response, ..item.data }) } @@ -1057,24 +1144,12 @@ impl ) -> Result { match item.response { CybersourcePaymentsResponse::ClientReferenceInformation(info_response) => { - let status = enums::AttemptStatus::foreign_from((info_response.status, false)); - let connector_response_reference_id = Some( - info_response - .client_reference_information - .code - .unwrap_or(info_response.id.clone()), - ); + let status = + enums::AttemptStatus::foreign_from((info_response.status.clone(), false)); + let response = get_payment_response((&info_response, status, item.http_code)); Ok(Self { status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(info_response.id), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id, - incremental_authorization_allowed: None, - }), + response, ..item.data }) } @@ -1213,9 +1288,10 @@ pub struct CybersourceApplicationInfoResponse { id: String, application_information: ApplicationInformation, client_reference_information: Option, + error_information: Option, } -#[derive(Debug, Deserialize)] +#[derive(Clone, Debug, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ApplicationInformation { status: CybersourcePaymentStatus, @@ -1248,24 +1324,48 @@ impl )); let incremental_authorization_allowed = Some(status == enums::AttemptStatus::Authorized); - Ok(Self { - status, - response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId( - app_response.id.clone(), + if is_payment_failure(status) { + let (message, reason) = match app_response.error_information { + Some(error_info) => ( + error_info + .message + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + error_info.reason, ), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - incremental_authorization_allowed, - connector_response_reference_id: app_response - .client_reference_information - .map(|cref| cref.code) - .unwrap_or(Some(app_response.id)), - }), - ..item.data - }) + None => (consts::NO_ERROR_MESSAGE.to_string(), None), + }; + Ok(Self { + response: Err(types::ErrorResponse { + code: consts::NO_ERROR_CODE.to_string(), + message, + reason, + status_code: item.http_code, + attempt_status: Some(enums::AttemptStatus::Failure), + connector_transaction_id: Some(app_response.id), + }), + status: enums::AttemptStatus::Failure, + ..item.data + }) + } else { + Ok(Self { + status, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + app_response.id.clone(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: app_response + .client_reference_information + .map(|cref| cref.code) + .unwrap_or(Some(app_response.id)), + incremental_authorization_allowed, + }), + ..item.data + }) + } } CybersourceTransactionResponse::ErrorInformation(error_response) => Ok(Self { status: item.data.status, diff --git a/crates/router/src/connector/iatapay/transformers.rs b/crates/router/src/connector/iatapay/transformers.rs index b6d2dee4a01b..e6ecc6da2ffe 100644 --- a/crates/router/src/connector/iatapay/transformers.rs +++ b/crates/router/src/connector/iatapay/transformers.rs @@ -1,16 +1,20 @@ use std::collections::HashMap; use api_models::enums::PaymentMethod; +use common_utils::errors::CustomResult; use masking::{Secret, SwitchStrategy}; use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{self, PaymentsAuthorizeRequestData, RefundsRequestData, RouterData}, + consts, core::errors, services, types::{self, api, storage::enums, PaymentsAuthorizeData}, }; +type Error = error_stack::Report; + // Every access token will be valid for 5 minutes. It contains grant_type and scope for different type of access, but for our usecases it should be only 'client_credentials' and 'payment' resp(as per doc) for all type of api call. #[derive(Debug, Serialize)] pub struct IatapayAuthUpdateRequest { @@ -257,53 +261,85 @@ pub struct IatapayPaymentsResponse { pub bank_transfer_description: Option, pub checkout_methods: Option, pub failure_code: Option, + pub failure_details: Option, +} + +fn get_iatpay_response( + response: IatapayPaymentsResponse, + status_code: u16, +) -> CustomResult< + ( + enums::AttemptStatus, + Option, + types::PaymentsResponseData, + ), + errors::ConnectorError, +> { + let status = enums::AttemptStatus::from(response.status); + let error = if status == enums::AttemptStatus::Failure { + Some(types::ErrorResponse { + code: response + .failure_code + .unwrap_or_else(|| consts::NO_ERROR_CODE.to_string()), + message: response + .failure_details + .clone() + .unwrap_or_else(|| consts::NO_ERROR_MESSAGE.to_string()), + reason: response.failure_details, + status_code, + attempt_status: Some(status), + connector_transaction_id: response.iata_payment_id.clone(), + }) + } else { + None + }; + let form_fields = HashMap::new(); + let id = match response.iata_payment_id.clone() { + Some(s) => types::ResponseId::ConnectorTransactionId(s), + None => types::ResponseId::NoResponseId, + }; + let connector_response_reference_id = response.merchant_payment_id.or(response.iata_payment_id); + + let payment_response_data = response.checkout_methods.map_or( + types::PaymentsResponseData::TransactionResponse { + resource_id: id.clone(), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: connector_response_reference_id.clone(), + incremental_authorization_allowed: None, + }, + |checkout_methods| types::PaymentsResponseData::TransactionResponse { + resource_id: id, + redirection_data: Some(services::RedirectForm::Form { + endpoint: checkout_methods.redirect.redirect_url, + method: services::Method::Get, + form_fields, + }), + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: connector_response_reference_id.clone(), + incremental_authorization_allowed: None, + }, + ); + Ok((status, error, payment_response_data)) } impl TryFrom> for types::RouterData { - type Error = error_stack::Report; + type Error = Error; fn try_from( item: types::ResponseRouterData, ) -> Result { - let form_fields = HashMap::new(); - let id = match item.response.iata_payment_id.clone() { - Some(s) => types::ResponseId::ConnectorTransactionId(s), - None => types::ResponseId::NoResponseId, - }; - let connector_response_reference_id = item - .response - .merchant_payment_id - .or(item.response.iata_payment_id); + let (status, error, payment_response_data) = + get_iatpay_response(item.response, item.http_code)?; Ok(Self { - status: enums::AttemptStatus::from(item.response.status), - response: item.response.checkout_methods.map_or( - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: id.clone(), - redirection_data: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: connector_response_reference_id.clone(), - incremental_authorization_allowed: None, - }), - |checkout_methods| { - Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: id, - redirection_data: Some(services::RedirectForm::Form { - endpoint: checkout_methods.redirect.redirect_url, - method: services::Method::Get, - form_fields, - }), - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: connector_response_reference_id.clone(), - incremental_authorization_allowed: None, - }) - }, - ), + status, + response: error.map_or_else(|| Ok(payment_response_data), Err), ..item.data }) } diff --git a/crates/router/src/connector/nmi/transformers.rs b/crates/router/src/connector/nmi/transformers.rs index 6146f4a45992..931dac5a9664 100644 --- a/crates/router/src/connector/nmi/transformers.rs +++ b/crates/router/src/connector/nmi/transformers.rs @@ -6,7 +6,10 @@ use masking::{ExposeInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::{self, PaymentsAuthorizeRequestData, PaymentsCompleteAuthorizeRequestData}, + connector::utils::{ + self, AddressDetailsData, PaymentsAuthorizeRequestData, + PaymentsCompleteAuthorizeRequestData, RouterData, + }, core::errors, services, types::{self, api, storage::enums, transformers::ForeignFrom, ConnectorAuthType}, @@ -83,6 +86,9 @@ pub struct NmiVaultRequest { security_key: Secret, ccnumber: CardNumber, ccexp: Secret, + cvv: Secret, + first_name: Secret, + last_name: Secret, customer_vault: CustomerAction, } @@ -97,12 +103,16 @@ impl TryFrom<&types::PaymentsPreProcessingRouterData> for NmiVaultRequest { type Error = Error; fn try_from(item: &types::PaymentsPreProcessingRouterData) -> Result { let auth_type: NmiAuthType = (&item.connector_auth_type).try_into()?; - let (ccnumber, ccexp) = get_card_details(item.request.payment_method_data.clone())?; + let (ccnumber, ccexp, cvv) = get_card_details(item.request.payment_method_data.clone())?; + let billing_details = item.get_billing_address()?; Ok(Self { security_key: auth_type.api_key, ccnumber, ccexp, + cvv, + first_name: billing_details.get_first_name()?.to_owned(), + last_name: billing_details.get_last_name()?.to_owned(), customer_vault: CustomerAction::AddCustomer, }) } @@ -110,7 +120,7 @@ impl TryFrom<&types::PaymentsPreProcessingRouterData> for NmiVaultRequest { fn get_card_details( payment_method_data: Option, -) -> CustomResult<(CardNumber, Secret), errors::ConnectorError> { +) -> CustomResult<(CardNumber, Secret, Secret), errors::ConnectorError> { match payment_method_data { Some(api::PaymentMethodData::Card(ref card_details)) => Ok(( card_details.card_number.clone(), @@ -118,6 +128,7 @@ fn get_card_details( card_details, "".to_string(), ), + card_details.card_cvc.clone(), )), _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("Nmi"), @@ -189,6 +200,7 @@ impl config: "public_key", }, )?, + order_id: item.data.connector_request_reference_id.clone(), }), mandate_reference: None, connector_metadata: None, @@ -220,13 +232,17 @@ impl #[derive(Debug, Serialize)] pub struct NmiCompleteRequest { + amount: f64, #[serde(rename = "type")] transaction_type: TransactionType, security_key: Secret, + orderid: String, + ccnumber: CardNumber, + ccexp: Secret, cardholder_auth: CardHolderAuthType, cavv: String, xid: String, - three_ds_version: ThreeDsVersion, + three_ds_version: Option, } #[derive(Debug, Serialize, Deserialize)] @@ -240,6 +256,8 @@ pub enum CardHolderAuthType { pub enum ThreeDsVersion { #[serde(rename = "2.0.0")] VersionTwo, + #[serde(rename = "2.1.0")] + VersionTwoPointOne, #[serde(rename = "2.2.0")] VersionTwoPointTwo, } @@ -250,7 +268,8 @@ pub struct NmiRedirectResponseData { cavv: String, xid: String, card_holder_auth: CardHolderAuthType, - three_ds_version: ThreeDsVersion, + three_ds_version: Option, + order_id: String, } impl TryFrom<&NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for NmiCompleteRequest { @@ -275,9 +294,16 @@ impl TryFrom<&NmiRouterData<&types::PaymentsCompleteAuthorizeRouterData>> for Nm field_name: "three_ds_data", })?; + let (ccnumber, ccexp, ..) = + get_card_details(item.router_data.request.payment_method_data.clone())?; + Ok(Self { + amount: item.amount, transaction_type, security_key: auth_type.api_key, + orderid: three_ds_data.order_id, + ccnumber, + ccexp, cardholder_auth: three_ds_data.card_holder_auth, cavv: three_ds_data.cavv, xid: three_ds_data.xid, diff --git a/crates/router/src/connector/volt.rs b/crates/router/src/connector/volt.rs index f239f5980145..bf36a7bff61e 100644 --- a/crates/router/src/connector/volt.rs +++ b/crates/router/src/connector/volt.rs @@ -223,7 +223,20 @@ impl ConnectorIntegration CustomResult { - self.build_error_response(res) + // auth error have different structure than common error + let response: volt::VoltAuthErrorResponse = res + .response + .parse_struct("VoltAuthErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code.to_string(), + message: response.message.clone(), + reason: Some(response.message), + attempt_status: None, + connector_transaction_id: None, + }) } } diff --git a/crates/router/src/connector/volt/transformers.rs b/crates/router/src/connector/volt/transformers.rs index cea56feb7145..9ee2a3f012eb 100644 --- a/crates/router/src/connector/volt/transformers.rs +++ b/crates/router/src/connector/volt/transformers.rs @@ -410,6 +410,12 @@ pub struct VoltErrorResponse { pub exception: VoltErrorException, } +#[derive(Debug, Deserialize)] +pub struct VoltAuthErrorResponse { + pub code: u64, + pub message: String, +} + #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] #[serde(rename_all = "camelCase")] pub struct VoltErrorException { diff --git a/crates/router/src/routes/customers.rs b/crates/router/src/routes/customers.rs index cfc37cbdbb2a..2592d8837d50 100644 --- a/crates/router/src/routes/customers.rs +++ b/crates/router/src/routes/customers.rs @@ -4,7 +4,7 @@ use router_env::{instrument, tracing, Flow}; use super::app::AppState; use crate::{ core::{api_locking, customers::*}, - services::{api, authentication as auth}, + services::{api, authentication as auth, authorization::permissions::Permission}, types::api::customers, }; @@ -36,7 +36,11 @@ pub async fn customers_create( &req, json_payload.into_inner(), |state, auth, req| create_customer(state, auth.merchant_account, auth.key_store, req), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -68,11 +72,14 @@ pub async fn customers_retrieve( }) .into_inner(); - let auth = + let auth = if auth::is_jwt_auth(req.headers()) { + Box::new(auth::JWTAuth(Permission::CustomerRead)) + } else { match auth::is_ephemeral_auth(req.headers(), &*state.store, &payload.customer_id).await { Ok(auth) => auth, Err(err) => return api::log_and_return_error_response(err), - }; + } + }; api::server_wrap( flow, @@ -110,7 +117,11 @@ pub async fn customers_list(state: web::Data, req: HttpRequest) -> Htt &req, (), |state, auth, _| list_customers(state, auth.merchant_account.merchant_id, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await @@ -148,7 +159,11 @@ pub async fn customers_update( &req, json_payload.into_inner(), |state, auth, req| update_customer(state, auth.merchant_account, req, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -185,7 +200,11 @@ pub async fn customers_delete( &req, payload, |state, auth, req| delete_customer(state, auth.merchant_account, req, auth.key_store), - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::CustomerWrite), + req.headers(), + ), api_locking::LockAction::NotApplicable, )) .await @@ -209,7 +228,11 @@ pub async fn get_customer_mandates( |state, auth, req| { crate::core::mandate::get_customer_mandates(state, auth.merchant_account, req) }, - &auth::ApiKeyAuth, + auth::auth_type( + &auth::ApiKeyAuth, + &auth::JWTAuth(Permission::MandateRead), + req.headers(), + ), api_locking::LockAction::NotApplicable, ) .await diff --git a/crates/router/src/services/api.rs b/crates/router/src/services/api.rs index a9fa574800ea..92fda578727c 100644 --- a/crates/router/src/services/api.rs +++ b/crates/router/src/services/api.rs @@ -778,6 +778,7 @@ pub enum RedirectForm { currency: Currency, public_key: Secret, customer_vault_id: String, + order_id: String, }, } @@ -1507,6 +1508,7 @@ pub fn build_redirection_form( currency, public_key, customer_vault_id, + order_id, } => { let public_key_val = public_key.peek(); maud::html! { @@ -1563,6 +1565,12 @@ pub fn build_redirection_form( item4.value=e.threeDsVersion; responseForm.appendChild(item4); + var item5=document.createElement('input'); + item4.type='hidden'; + item4.name='orderId'; + item4.value='{order_id}'; + responseForm.appendChild(item5); + document.body.appendChild(responseForm); responseForm.submit(); }}); diff --git a/crates/router/src/services/authorization/info.rs b/crates/router/src/services/authorization/info.rs index c6b649f3de5c..cef93f82739d 100644 --- a/crates/router/src/services/authorization/info.rs +++ b/crates/router/src/services/authorization/info.rs @@ -38,6 +38,7 @@ pub enum PermissionModule { Routing, Analytics, Mandates, + Customer, Disputes, Files, ThreeDsDecisionManager, @@ -55,6 +56,7 @@ impl PermissionModule { Self::Forex => "Forex module permissions allow the user to view and query the forex rates", Self::Analytics => "Permission to view and analyse the data relating to payments, refunds, sdk etc.", Self::Mandates => "Everything related to mandates - like creating and viewing mandate related information are within this module", + Self::Customer => "Everything related to customers - like creating and viewing customer related information are within this module", Self::Disputes => "Everything related to disputes - like creating and viewing dispute related information are within this module", Self::Files => "Permissions for uploading, deleting and viewing files for disputes", Self::ThreeDsDecisionManager => "View and configure 3DS decision rules configured for a merchant", @@ -133,6 +135,14 @@ impl ModuleInfo { Permission::MandateWrite, ]), }, + PermissionModule::Customer => Self { + module: module_name, + description, + permissions: PermissionInfo::new(&[ + Permission::CustomerRead, + Permission::CustomerWrite, + ]), + }, PermissionModule::Disputes => Self { module: module_name, description, diff --git a/crates/router/src/services/authorization/permissions.rs b/crates/router/src/services/authorization/permissions.rs index 708da97e1e39..426b048e88b7 100644 --- a/crates/router/src/services/authorization/permissions.rs +++ b/crates/router/src/services/authorization/permissions.rs @@ -19,6 +19,8 @@ pub enum Permission { DisputeWrite, MandateRead, MandateWrite, + CustomerRead, + CustomerWrite, FileRead, FileWrite, Analytics, @@ -55,6 +57,8 @@ impl Permission { Self::DisputeWrite => Some("Create and update disputes"), Self::MandateRead => Some("View mandates"), Self::MandateWrite => Some("Create and update mandates"), + Self::CustomerRead => Some("View customers"), + Self::CustomerWrite => Some("Create, update and delete customers"), Self::FileRead => Some("View files"), Self::FileWrite => Some("Create, update and delete files"), Self::Analytics => Some("Access to analytics module"), diff --git a/crates/router/src/services/authorization/predefined_permissions.rs b/crates/router/src/services/authorization/predefined_permissions.rs index a9f2b864d0ad..c489f1fc9638 100644 --- a/crates/router/src/services/authorization/predefined_permissions.rs +++ b/crates/router/src/services/authorization/predefined_permissions.rs @@ -52,6 +52,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -79,6 +81,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::Analytics, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::UsersRead, ], @@ -112,6 +115,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -150,6 +155,8 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::DisputeWrite, Permission::MandateRead, Permission::MandateWrite, + Permission::CustomerRead, + Permission::CustomerWrite, Permission::FileRead, Permission::FileWrite, Permission::Analytics, @@ -175,6 +182,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -198,6 +206,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -223,6 +232,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerRead, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -252,6 +262,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::SurchargeDecisionManagerWrite, Permission::DisputeRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::Analytics, Permission::UsersRead, @@ -273,6 +284,7 @@ pub static PREDEFINED_PERMISSIONS: Lazy> = Lazy: Permission::MerchantAccountRead, Permission::MerchantConnectorAccountRead, Permission::MandateRead, + Permission::CustomerRead, Permission::FileRead, Permission::FileWrite, Permission::Analytics, diff --git a/crates/router/src/types/domain/user.rs b/crates/router/src/types/domain/user.rs index a595afa4a27c..ce8350bd9eb0 100644 --- a/crates/router/src/types/domain/user.rs +++ b/crates/router/src/types/domain/user.rs @@ -788,6 +788,7 @@ impl From for user_role_api::PermissionModule { info::PermissionModule::Routing => Self::Routing, info::PermissionModule::Analytics => Self::Analytics, info::PermissionModule::Mandates => Self::Mandates, + info::PermissionModule::Customer => Self::Customer, info::PermissionModule::Disputes => Self::Disputes, info::PermissionModule::Files => Self::Files, info::PermissionModule::ThreeDsDecisionManager => Self::ThreeDsDecisionManager, diff --git a/crates/router/src/utils/user_role.rs b/crates/router/src/utils/user_role.rs index 0026984fdb9a..4449338402fb 100644 --- a/crates/router/src/utils/user_role.rs +++ b/crates/router/src/utils/user_role.rs @@ -74,6 +74,8 @@ impl TryFrom<&Permission> for user_role_api::Permission { Permission::DisputeWrite => Ok(Self::DisputeWrite), Permission::MandateRead => Ok(Self::MandateRead), Permission::MandateWrite => Ok(Self::MandateWrite), + Permission::CustomerRead => Ok(Self::CustomerRead), + Permission::CustomerWrite => Ok(Self::CustomerWrite), Permission::FileRead => Ok(Self::FileRead), Permission::FileWrite => Ok(Self::FileWrite), Permission::Analytics => Ok(Self::Analytics), diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario1-Create payment with confirm true/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js index e69de29bb2d1..97b68c987bdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/3DS Payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js index e69de29bb2d1..97b68c987bdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Cancel After Partial Capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js index e69de29bb2d1..97b68c987bdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js index e69de29bb2d1..97b68c987bdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Retrieve After Partial Capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js index e69de29bb2d1..97b68c987bdf 100644 --- a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario10-Multiple Captures/Successful Partial Capture and Refund/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario11-Save card flow/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario12-Don't Pass CVV for save card flow and verify success payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario13-Pass Invalid CVV for save card flow and verify failed payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario14-Save card payment with manual capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario2-Create payment with confirm false/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario3-Create payment without PMD/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario4-Create payment with Manual capture with confirm false/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario5-Create payment with Manual capture/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario6-Create Partial Capture payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario7-Void the payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario8-Refund full payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve-copy/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Happy Cases/Scenario9-Partial refund/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/QuickStart/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario3-Capture greater amount/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund exceeds amount/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file diff --git a/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js new file mode 100644 index 000000000000..97b68c987bdf --- /dev/null +++ b/postman/collection-dir/checkout/Flow Testcases/Variation Cases/Scenario6-Refund for unsuccessful payment/Payments - Retrieve/event.prerequest.js @@ -0,0 +1,3 @@ +setTimeout(function(){ + console.log("Sleeping for 3 seconds before next request."); +}, 3000); \ No newline at end of file