From 4647a2f6aece6b9479395fa3622b51b50d3091ee Mon Sep 17 00:00:00 2001 From: chikke srujan <121822803+srujanchikke@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:24:22 +0530 Subject: [PATCH] feat(connector): [Fiuu] Add support for cards recurring payments (#6361) Co-authored-by: Chikke Srujan Co-authored-by: hyperswitch-bot[bot] <148525504+hyperswitch-bot[bot]@users.noreply.github.com> --- config/deployments/integration_test.toml | 3 + config/deployments/production.toml | 4 + config/deployments/sandbox.toml | 4 + config/development.toml | 7 +- .../src/connectors/fiuu.rs | 48 ++- .../src/connectors/fiuu/transformers.rs | 276 ++++++++++++- crates/hyperswitch_connectors/src/utils.rs | 7 + crates/router/src/configs/defaults.rs | 36 ++ .../payments/operations/payment_create.rs | 6 +- .../cypress/e2e/PaymentUtils/Fiuu.js | 375 ++++++++++++++++++ cypress-tests/cypress/support/commands.js | 102 ++--- 11 files changed, 798 insertions(+), 70 deletions(-) diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index dafc6048154..651e2c25b64 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -341,6 +341,9 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } +[pm_filters.fiuu] +duit_now = { 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 caf8c030568..525033f06f2 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -354,6 +354,10 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } + +[pm_filters.fiuu] +duit_now = { 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 3346c4a2725..422095c65fb 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -358,6 +358,10 @@ red_pagos = { country = "UY", currency = "UYU" } [pm_filters.zsl] local_bank_transfer = { country = "CN", currency = "CNY" } + +[pm_filters.fiuu] +duit_now = { 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 8e5d9582e2f..d32c3610132 100644 --- a/config/development.toml +++ b/config/development.toml @@ -531,6 +531,9 @@ region = "" credit = { currency = "USD" } debit = { currency = "USD" } +[pm_filters.fiuu] +duit_now = { country ="MY", currency = "MYR" } + [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" } @@ -590,8 +593,8 @@ pay_later.klarna = { connector_list = "adyen" } wallet.google_pay = { connector_list = "stripe,adyen,cybersource,bankofamerica" } wallet.apple_pay = { connector_list = "stripe,adyen,cybersource,noon,bankofamerica" } wallet.paypal = { connector_list = "adyen" } -card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" } -card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree" } +card.credit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,fiuu" } +card.debit = { connector_list = "stripe,adyen,authorizedotnet,cybersource,globalpay,worldpay,multisafepay,nmi,nexinets,noon,bankofamerica,braintree,fiuu" } 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/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 31d87ceedc9..6bb65622a7b 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -1,6 +1,6 @@ pub mod transformers; -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; use common_enums::{CaptureMethod, PaymentMethodType}; use common_utils::{ @@ -12,6 +12,7 @@ use common_utils::{ }; use error_stack::ResultExt; use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, router_data::{AccessToken, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, @@ -43,7 +44,11 @@ use serde::{Deserialize, Serialize}; use serde_json::Value; use transformers::{self as fiuu, FiuuWebhooksResponse}; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, PaymentMethodDataType}, +}; fn parse_response(data: &[u8]) -> Result where @@ -210,6 +215,15 @@ impl ConnectorValidation for Fiuu { ), } } + fn validate_mandate_payment( + &self, + pm_type: Option, + pm_data: PaymentMethodData, + ) -> CustomResult<(), errors::ConnectorError> { + let mandate_supported_pmd: HashSet = + HashSet::from([PaymentMethodDataType::Card]); + utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) + } } impl ConnectorIntegration for Fiuu { @@ -231,13 +245,21 @@ impl ConnectorIntegration CustomResult { - Ok(format!( - "{}RMS/API/Direct/1.4.0/index.php", - self.base_url(connectors) - )) + let url = if req.request.off_session == Some(true) { + format!( + "{}/RMS/API/Recurring/input_v7.php", + self.base_url(connectors) + ) + } else { + format!( + "{}RMS/API/Direct/1.4.0/index.php", + self.base_url(connectors) + ) + }; + Ok(url) } fn get_request_body( @@ -252,9 +274,15 @@ impl ConnectorIntegration for FPXTxnChannel { } } +#[derive(Serialize, Debug, Clone)] +pub struct FiuuMandateRequest { + #[serde(rename = "0")] + mandate_request: Secret, +} + +#[derive(Serialize, Debug, Clone)] +pub struct FiuuRecurringRequest { + record_type: FiuuRecordType, + merchant_id: Secret, + token: Secret, + order_id: String, + currency: Currency, + amount: StringMajorUnit, + billing_name: Secret, + email: Email, + verify_key: Secret, +} + +#[derive(Serialize, Debug, Clone, strum::Display)] +pub enum FiuuRecordType { + T, +} + +impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuMandateRequest { + type Error = Report; + fn try_from(item: &FiuuRouterData<&PaymentsAuthorizeRouterData>) -> Result { + let auth: FiuuAuthType = FiuuAuthType::try_from(&item.router_data.connector_auth_type)?; + let record_type = FiuuRecordType::T; + let merchant_id = auth.merchant_id; + let order_id = item.router_data.connector_request_reference_id.clone(); + let currency = item.router_data.request.currency; + let amount = item.amount.clone(); + let billing_name = item.router_data.get_billing_full_name()?; + let email = item.router_data.request.get_email()?; + let token = Secret::new(item.router_data.request.get_connector_mandate_id()?); + let verify_key = auth.verify_key; + let recurring_request = FiuuRecurringRequest { + record_type: record_type.clone(), + merchant_id: merchant_id.clone(), + token: token.clone(), + order_id: order_id.clone(), + currency, + amount: amount.clone(), + billing_name: billing_name.clone(), + email: email.clone(), + verify_key: verify_key.clone(), + }; + let check_sum = calculate_check_sum(recurring_request)?; + let mandate_request = format!( + "{}|{}||{}|{}|{}|{}|{}|{}|||{}", + record_type, + merchant_id.peek(), + token.peek(), + order_id, + currency, + amount.get_amount_as_string(), + billing_name.peek(), + email.peek(), + check_sum.peek() + ); + Ok(Self { + mandate_request: mandate_request.into(), + }) + } +} + +pub fn calculate_check_sum( + req: FiuuRecurringRequest, +) -> CustomResult, errors::ConnectorError> { + let formatted_string = format!( + "{}{}{}{}{}{}{}", + req.record_type, + req.merchant_id.peek(), + req.token.peek(), + req.order_id, + req.currency, + req.amount.get_amount_as_string(), + req.verify_key.peek() + ); + Ok(Secret::new(hex::encode( + crypto::Md5 + .generate_digest(formatted_string.as_bytes()) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ))) +} + #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "PascalCase")] pub struct FiuuPaymentRequest { @@ -181,6 +271,8 @@ pub struct FiuuPaymentRequest { signature: Secret, #[serde(rename = "ReturnURL")] return_url: Option, + #[serde(rename = "NotificationURL")] + notification_url: Option, #[serde(flatten)] payment_method_data: FiuuPaymentMethodData, } @@ -219,6 +311,12 @@ pub struct FiuuCardData { cc_cvv2: Secret, cc_month: Secret, cc_year: Secret, + #[serde(rename = "mpstokenstatus")] + mps_token_status: Option, + #[serde(rename = "CustName")] + customer_name: Option>, + #[serde(rename = "CustEmail")] + customer_email: Option, } #[derive(Serialize, Debug, Clone)] @@ -278,7 +376,7 @@ pub fn calculate_signature( ) -> Result, Report> { let message = signature_data.as_bytes(); let encoded_data = hex::encode( - common_utils::crypto::Md5 + crypto::Md5 .generate_digest(message) .change_context(errors::ConnectorError::RequestEncodingFailed)?, ); @@ -307,8 +405,14 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque false => 1, true => 0, }; + let notification_url = Some( + Url::parse(&item.router_data.request.get_webhook_url()?) + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ); let payment_method_data = match item.router_data.request.payment_method_data { - PaymentMethodData::Card(ref card) => FiuuPaymentMethodData::try_from((card, &non_3ds)), + PaymentMethodData::Card(ref card) => { + FiuuPaymentMethodData::try_from((card, item.router_data)) + } PaymentMethodData::RealTimePayment(ref real_time_payment_data) => { match *real_time_payment_data.clone() { RealTimePaymentData::DuitNow {} => { @@ -434,20 +538,40 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque return_url, payment_method_data, signature, + notification_url, }) } } -impl TryFrom<(&Card, &i32)> for FiuuPaymentMethodData { +impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { type Error = Report; - fn try_from((req_card, non_3ds): (&Card, &i32)) -> Result { + fn try_from( + (req_card, item): (&Card, &PaymentsAuthorizeRouterData), + ) -> Result { + let (mps_token_status, customer_name, customer_email) = + if item.request.is_customer_initiated_mandate_payment() { + ( + Some(1), + Some(item.request.get_customer_name()?), + Some(item.request.get_email()?), + ) + } else { + (None, None, None) + }; + let non_3ds = match item.is_three_ds() { + false => 1, + true => 0, + }; Ok(Self::FiuuCardData(Box::new(FiuuCardData { txn_channel: TxnChannel::Creditan, - non_3ds: *non_3ds, + non_3ds, cc_pan: req_card.card_number.clone(), cc_cvv2: req_card.card_cvc.clone(), cc_month: req_card.card_exp_month.clone(), cc_year: req_card.card_exp_year.clone(), + mps_token_status, + customer_name, + customer_email, }))) } } @@ -543,6 +667,23 @@ pub enum FiuuPaymentsResponse { PaymentResponse(Box), QRPaymentResponse(Box), Error(FiuuErrorResponse), + RecurringResponse(Vec>), +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct FiuuRecurringResponse { + status: FiuuRecurringStautus, + #[serde(rename = "orderid")] + order_id: String, + #[serde(rename = "tranID")] + tran_id: Option, + reason: Option, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(rename_all = "snake_case")] +pub enum FiuuRecurringStautus { + Accepted, + Failed, } #[derive(Debug, Serialize, Deserialize)] @@ -581,10 +722,17 @@ pub struct NonThreeDSResponseData { #[serde(rename = "tranID")] pub tran_id: String, pub status: String, + #[serde(rename = "extraP")] + pub extra_parameters: Option, pub error_code: Option, pub error_desc: Option, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ExtraParameters { + token: Option>, +} + impl TryFrom< ResponseRouterData, @@ -652,6 +800,17 @@ impl }) } RequestData::NonThreeDS(non_threeds_data) => { + let mandate_reference = + non_threeds_data + .extra_parameters + .as_ref() + .and_then(|extra_p| { + extra_p.token.as_ref().map(|token| MandateReference { + connector_mandate_id: Some(token.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + }) + }); let status = match non_threeds_data.status.as_str() { "00" => { if item.data.request.is_auto_capture()? { @@ -685,7 +844,7 @@ impl Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(data.txn_id), 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, @@ -700,6 +859,80 @@ impl }) } }, + FiuuPaymentsResponse::RecurringResponse(ref recurring_response_vec) => { + let recurring_response_item = recurring_response_vec.first(); + let router_data_response = match recurring_response_item { + Some(recurring_response) => { + let status = + common_enums::AttemptStatus::from(recurring_response.status.clone()); + let connector_transaction_id = recurring_response + .tran_id + .as_ref() + .map_or(ResponseId::NoResponseId, |tran_id| { + ResponseId::ConnectorTransactionId(tran_id.clone()) + }); + let response = if status == common_enums::AttemptStatus::Failure { + Err(ErrorResponse { + code: recurring_response + .reason + .clone() + .unwrap_or_else(|| "NO_ERROR_CODE".to_string()), + message: recurring_response + .reason + .clone() + .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()), + reason: recurring_response.reason.clone(), + status_code: item.http_code, + attempt_status: None, + connector_transaction_id: recurring_response.tran_id.clone(), + }) + } else { + Ok(PaymentsResponseData::TransactionResponse { + resource_id: connector_transaction_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, + }) + }; + Self { + status, + response, + ..item.data + } + } + None => { + // It is not expected to get empty response from the connnector, if we get we are not updating the payment response since we don't have any info in the authorize response. + let response = Ok(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, + charge_id: None, + }); + Self { + response, + ..item.data + } + } + }; + Ok(router_data_response) + } + } + } +} + +impl From for common_enums::AttemptStatus { + fn from(status: FiuuRecurringStautus) -> Self { + match status { + FiuuRecurringStautus::Accepted => Self::Charged, + FiuuRecurringStautus::Failed => Self::Failure, } } } @@ -943,6 +1176,27 @@ impl TryFrom> for PaymentsSy capture_method: item.data.request.capture_method, status: response.status, })?; + let mandate_reference = response.extra_parameters.as_ref().and_then(|extra_p| { + let mandate_token: Result = serde_json::from_str(extra_p); + match mandate_token { + Ok(token) => { + token.token.as_ref().map(|token| MandateReference { + connector_mandate_id: Some(token.clone().expose()), + payment_method_id: None, + mandate_metadata: None, + }) + } + Err(err) => { + router_env::logger::warn!( + "Failed to convert 'extraP' from fiuu webhook response to fiuu::ExtraParameters. \ + Input: '{}', Error: {}", + extra_p, + err + ); + None + } + } + }); let error_response = if status == enums::AttemptStatus::Failure { Some(ErrorResponse { status_code: item.http_code, @@ -964,7 +1218,7 @@ impl TryFrom> for PaymentsSy let payments_response_data = PaymentsResponseData::TransactionResponse { resource_id: item.data.request.connector_transaction_id.clone(), 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, @@ -1417,6 +1671,8 @@ pub struct FiuuWebhooksPaymentResponse { pub channel: String, pub error_desc: Option, pub error_code: Option, + #[serde(rename = "extraP")] + pub extra_parameters: Option, } #[derive(Debug, Deserialize, Serialize, Clone)] diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 652ec82c5ed..fb2e1cce44c 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1117,6 +1117,7 @@ pub trait PaymentsAuthorizeRequestData { fn get_total_surcharge_amount(&self) -> Option; fn get_metadata_as_object(&self) -> Option; fn get_authentication_data(&self) -> Result; + fn get_customer_name(&self) -> Result, Error>; } impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { @@ -1271,6 +1272,12 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .clone() .ok_or_else(missing_field_err("authentication_data")) } + + fn get_customer_name(&self) -> Result, Error> { + self.customer_name + .clone() + .ok_or_else(missing_field_err("customer_name")) + } } pub trait PaymentsCaptureRequestData { diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index e94ad375776..c48d2fe54ca 100644 --- a/crates/router/src/configs/defaults.rs +++ b/crates/router/src/configs/defaults.rs @@ -423,6 +423,24 @@ impl Default for super::settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Fiuu, + RequiredFieldFinal { + mandate: 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, + } + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), ( enums::Connector::Authorizedotnet, RequiredFieldFinal { @@ -3502,6 +3520,24 @@ impl Default for super::settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Fiuu, + RequiredFieldFinal { + mandate: 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, + } + ) + ]), + non_mandate: HashMap::new(), + common: HashMap::new(), + } + ), ( enums::Connector::Authorizedotnet, RequiredFieldFinal { diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 46799c3c196..3bccce2783a 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -955,7 +955,11 @@ impl ValidateRequest> f helpers::validate_customer_id_mandatory_cases( request.setup_future_usage.is_some(), - request.customer_id.as_ref(), + request.customer_id.as_ref().or(request + .customer + .as_ref() + .map(|customer| customer.id.clone()) + .as_ref()), )?; } diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 86e2314c927..24a445dd3c2 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -13,6 +13,40 @@ const successfulThreeDSTestCardDetails = { card_holder_name: "joseph Doe", card_cvc: "123", }; + +const singleUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + single_use: { + amount: 8000, + currency: "USD", + }, + }, +}; + +const multiUseMandateData = { + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + mandate_type: { + multi_use: { + amount: 8000, + currency: "USD", + }, + }, +}; export const connectorDetails = { card_pm: { PaymentIntent: { @@ -184,5 +218,346 @@ export const connectorDetails = { }, }, }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: multiUseMandateData, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + 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", + 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", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_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", + }, + }, + }, + SaveCardConfirmAutoCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + mandate_data: null, + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, }, }; diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index a6436e7f67c..9b938e673d7 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -1843,7 +1843,11 @@ Cypress.Commands.add( const nextActionUrl = response.body.next_action.redirect_to_url; cy.log(nextActionUrl); } else if (response.body.authentication_type === "no_three_ds") { - expect(response.body.status).to.equal("succeeded"); + if (response.body.connector === "fiuu") { + expect(response.body.status).to.equal("failed"); + } else { + expect(response.body.status).to.equal("succeeded"); + } } else { throw new Error( `Invalid authentication type ${response.body.authentication_type}` @@ -2446,51 +2450,55 @@ Cypress.Commands.add( } ); -Cypress.Commands.add("updateConfig", (configType, configData, globalState, value) => { - const base_url = globalState.get("baseUrl"); - const merchant_id = globalState.get("merchantId"); - const api_key = globalState.get("adminApiKey"); - - let key; - let url; - let body; - - switch (configType) { - case 'autoRetry': - key = `should_call_gsm_${merchant_id}`; - url = `${base_url}/configs/${key}`; - body = { key: key, value: value }; - break; - case 'maxRetries': - key = `max_auto_retries_enabled_${merchant_id}`; - url = `${base_url}/configs/${key}`; - body = { key: key, value: value }; - break; - case 'stepUp': - key = `step_up_enabled_${merchant_id}`; - url = `${base_url}/configs/${key}`; - body = { key: key, value: value }; - break; - default: - throw new Error(`Invalid config type passed into the configs: "${api_key}: ${value}"`); - } - - cy.request({ - method: 'POST', - url: url, - headers: { - "Content-Type": "application/json", - "api-key": api_key, - }, - body: body, - failOnStatusCode: false, - }).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); +Cypress.Commands.add( + "updateConfig", + (configType, configData, globalState, value) => { + const base_url = globalState.get("baseUrl"); + const merchant_id = globalState.get("merchantId"); + const api_key = globalState.get("adminApiKey"); + + let key; + let url; + let body; + + switch (configType) { + case "autoRetry": + key = `should_call_gsm_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + case "maxRetries": + key = `max_auto_retries_enabled_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + case "stepUp": + key = `step_up_enabled_${merchant_id}`; + url = `${base_url}/configs/${key}`; + body = { key: key, value: value }; + break; + default: + throw new Error( + `Invalid config type passed into the configs: "${api_key}: ${value}"` + ); } - }); -}); + cy.request({ + method: "POST", + url: url, + headers: { + "Content-Type": "application/json", + "api-key": api_key, + }, + body: body, + failOnStatusCode: false, + }).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); + } + }); + } +);