From bd24d3186fc88b813ae33f0b3789c018d1ba76f5 Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 18 Oct 2024 09:07:16 +0530 Subject: [PATCH 01/11] add mandates support for fiuu --- config/deployments/integration_test.toml | 3 + config/deployments/production.toml | 4 + config/deployments/sandbox.toml | 4 + config/development.toml | 7 +- .../src/connectors/fiuu.rs | 49 ++++- .../src/connectors/fiuu/transformers.rs | 180 ++++++++++++++++-- crates/hyperswitch_connectors/src/utils.rs | 7 + 7 files changed, 229 insertions(+), 25 deletions(-) diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 4f742a61698b..bf4230001f5f 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -339,6 +339,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 c25b70d6ba08..be22704e9965 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -352,6 +352,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 28f5a4e05759..2c13f4c88f6b 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -356,6 +356,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 0153c6ef102d..100cf178f862 100644 --- a/config/development.toml +++ b/config/development.toml @@ -529,6 +529,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" } @@ -588,8 +591,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 31d87ceedc93..ed537d7b241e 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,16 @@ 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 +246,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 +275,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.get_billing_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 { @@ -219,6 +281,10 @@ 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>, } #[derive(Serialize, Debug, Clone)] @@ -278,7 +344,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)?, ); @@ -308,7 +374,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque true => 0, }; 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 {} => { @@ -438,16 +504,27 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque } } -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) = if item.request.is_customer_initiated_mandate_payment() { + (Some(1), Some(item.request.get_customer_name()?)) + } else { + (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, }))) } } @@ -543,6 +620,23 @@ pub enum FiuuPaymentsResponse { PaymentResponse(Box), QRPaymentResponse(Box), Error(FiuuErrorResponse), + RecurringResponse(Box), +} + +#[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 +675,17 @@ pub struct NonThreeDSResponseData { #[serde(rename = "tranID")] pub tran_id: String, pub status: String, + #[serde(rename = "extraP")] + pub extra_parmeters : Option, pub error_code: Option, pub error_desc: Option, } +#[derive(Debug, Serialize, Deserialize)] +pub struct ExtraParmeters{ + token : Option>, +} + impl TryFrom< ResponseRouterData, @@ -652,6 +753,11 @@ impl }) } RequestData::NonThreeDS(non_threeds_data) => { + let mandate_reference = non_threeds_data.extra_parmeters.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 +791,7 @@ impl Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(data.txn_id), redirection_data: None, - mandate_reference: None, + mandate_reference, connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -700,10 +806,58 @@ impl }) } }, + FiuuPaymentsResponse::RecurringResponse(ref 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: None, + mandate_reference:None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charge_id: None, + }) + }; + Ok(Self { + status, + response, + ..item.data + }) + }, } } } +impl From for common_enums::AttemptStatus{ + fn from(status :FiuuRecurringStautus )-> Self { + match status{ + FiuuRecurringStautus::Accepted => Self::Charged, + FiuuRecurringStautus::Failed => Self::Failure, + } + } +} + + #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] pub struct FiuuRefundRequest { diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 0134253f2f95..f9a58f2b242c 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1083,6 +1083,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 { @@ -1237,6 +1238,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 { From fcab28c49abc0710839e584cd44a25bd75e1d8d4 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:39:58 +0000 Subject: [PATCH 02/11] chore: run formatter --- .../src/connectors/fiuu.rs | 5 +- .../src/connectors/fiuu/transformers.rs | 149 +++++++++++------- 2 files changed, 97 insertions(+), 57 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index ed537d7b241e..6bb65622a7b4 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -220,9 +220,8 @@ impl ConnectorValidation for Fiuu { pm_type: Option, pm_data: PaymentMethodData, ) -> CustomResult<(), errors::ConnectorError> { - let mandate_supported_pmd: HashSet = HashSet::from([ - PaymentMethodDataType::Card, - ]); + let mandate_supported_pmd: HashSet = + HashSet::from([PaymentMethodDataType::Card]); utils::is_mandate_supported(pm_data, pm_type, mandate_supported_pmd, self.id()) } } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index fff43225d507..04e293f0d56f 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -4,9 +4,12 @@ use api_models::payments; use cards::CardNumber; use common_enums::{enums, BankNames, CaptureMethod, Currency}; use common_utils::{ - crypto::{self, GenerateDigest}, errors::CustomResult, ext_traits::Encode - - , pii::Email, request::Method, types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector} + crypto::{self, GenerateDigest}, + errors::CustomResult, + ext_traits::Encode, + pii::Email, + request::Method, + types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}, }; use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ @@ -19,7 +22,9 @@ use hyperswitch_domain_models::{ }, router_flow_types::refunds::{Execute, RSync}, router_request_types::{PaymentsAuthorizeData, ResponseId}, - router_response_types::{MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, RefundSyncRouterData, RefundsRouterData, @@ -168,27 +173,26 @@ impl TryFrom for FPXTxnChannel { } #[derive(Serialize, Debug, Clone)] -pub struct FiuuMandateRequest{ +pub struct FiuuMandateRequest { #[serde(rename = "0")] - mandate_request : Secret + mandate_request: Secret, } #[derive(Serialize, Debug, Clone)] -pub struct FiuuRecurringRequest{ - record_type : FiuuRecordType, - merchant_id : Secret, +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 + verify_key: Secret, } - #[derive(Serialize, Debug, Clone, strum::Display)] -pub enum FiuuRecordType{ +pub enum FiuuRecordType { T, } @@ -205,30 +209,54 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuMandateReque let email = item.router_data.get_billing_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(), + let recurring_request = FiuuRecurringRequest { + record_type: record_type.clone(), + merchant_id: merchant_id.clone(), token: token.clone(), - order_id :order_id.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(), + 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)?))) +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)] @@ -284,7 +312,7 @@ pub struct FiuuCardData { #[serde(rename = "mpstokenstatus")] mps_token_status: Option, #[serde(rename = "CustName")] - customer_name :Option>, + customer_name: Option>, } #[derive(Serialize, Debug, Clone)] @@ -374,7 +402,9 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque true => 0, }; let payment_method_data = match item.router_data.request.payment_method_data { - PaymentMethodData::Card(ref card) => FiuuPaymentMethodData::try_from((card, item.router_data)), + 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 {} => { @@ -506,12 +536,15 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { type Error = Report; - fn try_from((req_card, item): (&Card, &PaymentsAuthorizeRouterData)) -> Result { - let (mps_token_status,customer_name) = if item.request.is_customer_initiated_mandate_payment() { - (Some(1), Some(item.request.get_customer_name()?)) - } else { - (None,None) - }; + fn try_from( + (req_card, item): (&Card, &PaymentsAuthorizeRouterData), + ) -> Result { + let (mps_token_status, customer_name) = + if item.request.is_customer_initiated_mandate_payment() { + (Some(1), Some(item.request.get_customer_name()?)) + } else { + (None, None) + }; let non_3ds = match item.is_three_ds() { false => 1, true => 0, @@ -624,17 +657,17 @@ pub enum FiuuPaymentsResponse { } #[derive(Debug, Serialize, Deserialize)] -pub struct FiuuRecurringResponse{ +pub struct FiuuRecurringResponse { status: FiuuRecurringStautus, #[serde(rename = "orderid")] order_id: String, #[serde(rename = "tranID")] tran_id: Option, - reason: Option + reason: Option, } #[derive(Debug, Serialize, Deserialize, Clone)] #[serde(rename_all = "snake_case")] -pub enum FiuuRecurringStautus{ +pub enum FiuuRecurringStautus { Accepted, Failed, } @@ -676,14 +709,14 @@ pub struct NonThreeDSResponseData { pub tran_id: String, pub status: String, #[serde(rename = "extraP")] - pub extra_parmeters : Option, + pub extra_parmeters: Option, pub error_code: Option, pub error_desc: Option, } #[derive(Debug, Serialize, Deserialize)] -pub struct ExtraParmeters{ - token : Option>, +pub struct ExtraParmeters { + token: Option>, } impl @@ -753,11 +786,17 @@ impl }) } RequestData::NonThreeDS(non_threeds_data) => { - let mandate_reference = non_threeds_data.extra_parmeters.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 mandate_reference = + non_threeds_data + .extra_parmeters + .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()? { @@ -808,9 +847,12 @@ impl }, FiuuPaymentsResponse::RecurringResponse(ref 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 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 @@ -818,7 +860,7 @@ impl .clone() .unwrap_or_else(|| "NO_ERROR_CODE".to_string()), message: recurring_response - .reason + .reason .clone() .unwrap_or_else(|| "NO_ERROR_MESSAGE".to_string()), reason: recurring_response.reason.clone(), @@ -830,7 +872,7 @@ impl Ok(PaymentsResponseData::TransactionResponse { resource_id: connector_transaction_id, redirection_data: None, - mandate_reference:None, + mandate_reference: None, connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -843,21 +885,20 @@ impl response, ..item.data }) - }, + } } } } -impl From for common_enums::AttemptStatus{ - fn from(status :FiuuRecurringStautus )-> Self { - match status{ +impl From for common_enums::AttemptStatus { + fn from(status: FiuuRecurringStautus) -> Self { + match status { FiuuRecurringStautus::Accepted => Self::Charged, FiuuRecurringStautus::Failed => Self::Failure, } } } - #[derive(Debug, Serialize)] #[serde(rename_all = "PascalCase")] pub struct FiuuRefundRequest { From f00a4354092581dd3a21cb617b13e135f5ed9daa Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 18 Oct 2024 09:15:54 +0530 Subject: [PATCH 03/11] spell check fix --- .../src/connectors/fiuu/transformers.rs | 22 +++++++------------ 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 04e293f0d56f..5974c209c702 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -709,14 +709,14 @@ pub struct NonThreeDSResponseData { pub tran_id: String, pub status: String, #[serde(rename = "extraP")] - pub extra_parmeters: Option, + pub extra_parameters : Option, pub error_code: Option, pub error_desc: Option, } #[derive(Debug, Serialize, Deserialize)] -pub struct ExtraParmeters { - token: Option>, +pub struct ExtraParameters{ + token : Option>, } impl @@ -786,17 +786,11 @@ impl }) } RequestData::NonThreeDS(non_threeds_data) => { - let mandate_reference = - non_threeds_data - .extra_parmeters - .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 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()? { From 29a04db2c127dfbeee5844b7ce575055fdfe995b Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Fri, 18 Oct 2024 03:49:38 +0000 Subject: [PATCH 04/11] chore: run formatter --- .../src/connectors/fiuu/transformers.rs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 5974c209c702..4022ab4e27be 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -709,14 +709,14 @@ pub struct NonThreeDSResponseData { pub tran_id: String, pub status: String, #[serde(rename = "extraP")] - pub extra_parameters : Option, + pub extra_parameters: Option, pub error_code: Option, pub error_desc: Option, } #[derive(Debug, Serialize, Deserialize)] -pub struct ExtraParameters{ - token : Option>, +pub struct ExtraParameters { + token: Option>, } impl @@ -786,11 +786,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 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()? { From 0208f7417c882481a675adbc9ee8cf917c1d8bc7 Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 18 Oct 2024 12:26:12 +0530 Subject: [PATCH 05/11] payment response fix --- .../src/connectors/fiuu/transformers.rs | 104 +++++++++++------- 1 file changed, 64 insertions(+), 40 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 4022ab4e27be..2d158064e6ac 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -653,7 +653,7 @@ pub enum FiuuPaymentsResponse { PaymentResponse(Box), QRPaymentResponse(Box), Error(FiuuErrorResponse), - RecurringResponse(Box), + RecurringResponse(Vec>), } #[derive(Debug, Serialize, Deserialize)] @@ -845,46 +845,70 @@ impl }) } }, - FiuuPaymentsResponse::RecurringResponse(ref 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: None, - mandate_reference: None, - connector_metadata: None, - network_txn_id: None, - connector_response_reference_id: None, - incremental_authorization_allowed: None, - charge_id: None, - }) + 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: None, + mandate_reference: 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: None, + mandate_reference: 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(Self { - status, - response, - ..item.data - }) + Ok(router_data_response) } } } From 6b9d025fe82ca0d532b334368eea06801a3c44ca Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 18 Oct 2024 15:06:20 +0530 Subject: [PATCH 06/11] add support for 3ds mandates --- .../src/connectors/fiuu/transformers.rs | 34 +++++++++++++++++-- 1 file changed, 32 insertions(+), 2 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 2d158064e6ac..c9c9c2e25fe3 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -271,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, } @@ -401,6 +403,10 @@ 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, item.router_data)) @@ -530,6 +536,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque return_url, payment_method_data, signature, + notification_url, }) } } @@ -714,7 +721,7 @@ pub struct NonThreeDSResponseData { pub error_desc: Option, } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub struct ExtraParameters { token: Option>, } @@ -1162,6 +1169,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, @@ -1183,7 +1211,7 @@ impl TryFrom> for PaymentsSy let payments_response_data = PaymentsResponseData::TransactionResponse { resource_id: item.data.request.connector_transaction_id.clone(), redirection_data: None, - mandate_reference: None, + mandate_reference, connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, @@ -1636,6 +1664,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)] From fddc713f1388ca421b5c3c404308993eae8e6a28 Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 18 Oct 2024 17:31:27 +0530 Subject: [PATCH 07/11] format fix --- .../src/connectors/fiuu/transformers.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index c9c9c2e25fe3..2b429086eb8c 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -1183,10 +1183,10 @@ impl TryFrom> for PaymentsSy router_env::logger::warn!( "Failed to convert 'extraP' from fiuu webhook response to fiuu::ExtraParameters. \ Input: '{}', Error: {}", - extra_p, + extra_p, err ); - None + None } } }); From b55431244f3944b90aaf30f8a6204c82ab271265 Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 25 Oct 2024 02:25:37 +0530 Subject: [PATCH 08/11] add tests for fiuu mandates --- .../src/connectors/fiuu/transformers.rs | 15 +- crates/router/src/configs/defaults.rs | 36 ++ .../payments/operations/payment_create.rs | 2 +- .../cypress/e2e/PaymentUtils/Fiuu.js | 331 ++++++++++++++++++ cypress-tests/cypress/support/commands.js | 102 +++--- 5 files changed, 434 insertions(+), 52 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 2b429086eb8c..3825b50fe34c 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -206,7 +206,7 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuMandateReque 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.get_billing_email()?; + 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 { @@ -315,6 +315,8 @@ pub struct FiuuCardData { mps_token_status: Option, #[serde(rename = "CustName")] customer_name: Option>, + #[serde(rename = "CustEmail")] + customer_email: Option, } #[derive(Serialize, Debug, Clone)] @@ -546,11 +548,15 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { fn try_from( (req_card, item): (&Card, &PaymentsAuthorizeRouterData), ) -> Result { - let (mps_token_status, customer_name) = + 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(1), + Some(item.request.get_customer_name()?), + Some(item.request.get_email()?), + ) } else { - (None, None) + (None, None, None) }; let non_3ds = match item.is_three_ds() { false => 1, @@ -565,6 +571,7 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { cc_year: req_card.card_exp_year.clone(), mps_token_status, customer_name, + customer_email, }))) } } diff --git a/crates/router/src/configs/defaults.rs b/crates/router/src/configs/defaults.rs index e94ad375776e..c48d2fe54caa 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 fc1f71c2085d..97f4dd5e59f4 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -955,7 +955,7 @@ 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().and_then(|customer|Some(customer.id.clone())).as_ref()), )?; } diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 86e2314c9279..cbf8593e076e 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,302 @@ 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", + }, + }, + }, + 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: { + error_code: "No error code", + error_message: + "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + }, + }, + }, + SaveCardConfirmManualCaptureOffSession: { + Request: { + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + error_code: "No error code", + error_message: + "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + }, + }, + }, + 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 a6436e7f67c2..9b938e673d77 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); + } + }); + } +); From d72aec8d1b191ce46ca50d10a3f7123c02016d30 Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 21:58:00 +0000 Subject: [PATCH 09/11] chore: run formatter --- .../router/src/core/payments/operations/payment_create.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index 99235bae673b..d5888a382312 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().or(request.customer.as_ref().and_then(|customer|Some(customer.id.clone())).as_ref()), + request.customer_id.as_ref().or(request + .customer + .as_ref() + .and_then(|customer| Some(customer.id.clone())) + .as_ref()), )?; } From 0371c95770a019bf42584091004258a883fc2050 Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 25 Oct 2024 10:04:57 +0530 Subject: [PATCH 10/11] clippy fix --- crates/router/src/core/payments/operations/payment_create.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/router/src/core/payments/operations/payment_create.rs b/crates/router/src/core/payments/operations/payment_create.rs index d5888a382312..3bccce2783af 100644 --- a/crates/router/src/core/payments/operations/payment_create.rs +++ b/crates/router/src/core/payments/operations/payment_create.rs @@ -958,7 +958,7 @@ impl ValidateRequest> f request.customer_id.as_ref().or(request .customer .as_ref() - .and_then(|customer| Some(customer.id.clone())) + .map(|customer| customer.id.clone()) .as_ref()), )?; } From 82b2c403fa3eae313051329063f6c49a9ff90e6f Mon Sep 17 00:00:00 2001 From: Chikke Srujan Date: Fri, 25 Oct 2024 16:20:27 +0530 Subject: [PATCH 11/11] fix saved cards flow tests --- .../cypress/e2e/PaymentUtils/Fiuu.js | 56 +++++++++++++++++-- 1 file changed, 50 insertions(+), 6 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index cbf8593e076e..24a445dd3c25 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -370,6 +370,54 @@ export const connectorDetails = { }, }, }, + 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", @@ -423,9 +471,7 @@ export const connectorDetails = { Response: { status: 200, body: { - error_code: "No error code", - error_message: - "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + status: "succeeded", }, }, }, @@ -436,9 +482,7 @@ export const connectorDetails = { Response: { status: 200, body: { - error_code: "No error code", - error_message: - "You cannot confirm with `off_session=true` when `setup_future_usage` is also set on the PaymentIntent. The customer needs to be on-session to perform the steps which may be required to set up the PaymentMethod for future usage. Please confirm this PaymentIntent with your customer on-session.", + status: "requires_capture", }, }, },