From 4347688bbd6c12f181e20056e60ec425ca58f3a2 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Dec 2024 16:50:23 +0530 Subject: [PATCH 1/9] feat(connectors): fiuu,novalnet,worldpay - extend NTI flows --- config/config.example.toml | 2 +- config/deployments/integration_test.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- .../src/connectors/fiuu/transformers.rs | 7 +- .../src/connectors/novalnet/transformers.rs | 51 +++++++++++ .../src/connectors/worldpay.rs | 36 ++++++++ .../src/connectors/worldpay/requests.rs | 24 +++-- .../src/connectors/worldpay/response.rs | 8 +- .../src/connectors/worldpay/transformers.rs | 71 +++++++++++---- crates/hyperswitch_connectors/src/utils.rs | 88 ++++++++++++++++++- loadtest/config/development.toml | 2 +- 13 files changed, 264 insertions(+), 33 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 999266321144..f5cb5683e194 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -803,7 +803,7 @@ check_token_status_url= "" # base url to check token status from token servic connector_list = "cybersource" # Supported connectors for network tokenization [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" # Supported connectors for network transaction id +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" # Supported connectors for network transaction id [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index dd4938d8d6f8..583c5edca51a 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [payouts] diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index d2132cd1e400..08b4c141bc30 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [payouts] diff --git a/config/development.toml b/config/development.toml index d157894ac766..6957ae375578 100644 --- a/config/development.toml +++ b/config/development.toml @@ -654,7 +654,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 3bbb11063505..5564e92f6c5c 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -540,7 +540,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 143cd1aa0474..d685bf153abe 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -1092,6 +1092,8 @@ pub struct FiuuPaymentSyncResponse { error_desc: String, #[serde(rename = "miscellaneous")] miscellaneous: Option>>, + #[serde(rename = "SchemeTransactionID")] + scheme_transaction_id: Option>, } #[derive(Debug, Serialize, Deserialize, Display, Clone, PartialEq)] @@ -1182,7 +1184,10 @@ impl TryFrom> for PaymentsSy redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, - network_txn_id: None, + network_txn_id: response + .scheme_transaction_id + .as_ref() + .map(|id| id.clone().expose()), connector_response_reference_id: None, incremental_authorization_allowed: None, charge_id: None, diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 0a70393a13cd..c59837f6f70c 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -88,6 +88,13 @@ pub struct NovalnetCard { card_holder: Secret, } +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct NovalnetRawCardDetails { + card_number: CardNumber, + card_expiry_month: Secret, + card_expiry_year: Secret, +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub struct NovalnetMandate { token: Secret, @@ -107,6 +114,7 @@ pub struct NovalnetApplePay { #[serde(untagged)] pub enum NovalNetPaymentData { Card(NovalnetCard), + RawCardForNTI(NovalnetRawCardDetails), GooglePay(NovalnetGooglePay), ApplePay(NovalnetApplePay), MandatePayment(NovalnetMandate), @@ -130,6 +138,7 @@ pub struct NovalnetPaymentsRequestTransaction { error_return_url: Option, enforce_3d: Option, //NOTE: Needed for CREDITCARD, GOOGLEPAY create_token: Option, + scheme_tid: Option>, // Card network's transaction ID } #[derive(Debug, Serialize, Clone)] @@ -243,6 +252,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_card), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -274,6 +284,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_google_pay), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -299,6 +310,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym })), enforce_3d: None, create_token, + scheme_tid: None, }; Ok(Self { @@ -340,6 +352,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: None, enforce_3d: None, create_token, + scheme_tid: None, }; Ok(Self { merchant, @@ -398,6 +411,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym payment_data: Some(novalnet_mandate_data), enforce_3d, create_token: None, + scheme_tid: None, }; Ok(Self { @@ -407,6 +421,43 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym custom, }) } + Some(api_models::payments::MandateReferenceId::NetworkMandateId( + network_transaction_id, + )) => match item.router_data.request.payment_method_data { + PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { + let novalnet_card = NovalNetPaymentData::RawCardForNTI(NovalnetRawCardDetails { + card_number: raw_card_details.card_number.clone(), + card_expiry_month: raw_card_details.card_exp_month.clone(), + card_expiry_year: raw_card_details.card_exp_year.clone(), + }); + + let transaction = NovalnetPaymentsRequestTransaction { + test_mode, + payment_type: NovalNetPaymentTypes::CREDITCARD, + amount: item.amount.clone(), + currency: item.router_data.request.currency, + order_no: item.router_data.connector_request_reference_id.clone(), + hook_url: Some(hook_url), + return_url: Some(return_url.clone()), + error_return_url: Some(return_url.clone()), + payment_data: Some(novalnet_card), + enforce_3d, + create_token, + scheme_tid: Some(network_transaction_id.into()), + }; + + Ok(Self { + merchant, + transaction, + customer, + custom, + }) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("novalnet"), + ) + .into()), + }, _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("novalnet"), ) diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay.rs b/crates/hyperswitch_connectors/src/connectors/worldpay.rs index 07e4c178f9b8..104e854e6fee 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay.rs @@ -1199,6 +1199,42 @@ impl IncomingWebhook for Worldpay { let psync_body = WorldpayEventResponse::try_from(body)?; Ok(Box::new(psync_body)) } + + fn get_mandate_details( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let body: WorldpayWebhookTransactionId = request + .body + .parse_struct("WorldpayWebhookTransactionId") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + let mandate_reference = body.event_details.token.map(|mandate_token| { + hyperswitch_domain_models::router_flow_types::ConnectorMandateDetails { + connector_mandate_id: mandate_token.href, + } + }); + Ok(mandate_reference) + } + + fn get_network_txn_id( + &self, + request: &IncomingWebhookRequestDetails<'_>, + ) -> CustomResult< + Option, + errors::ConnectorError, + > { + let body: WorldpayWebhookTransactionId = request + .body + .parse_struct("WorldpayWebhookTransactionId") + .change_context(errors::ConnectorError::WebhookReferenceIdNotFound)?; + let optional_network_txn_id = body.event_details.scheme_reference.map(|network_txn_id| { + hyperswitch_domain_models::router_flow_types::ConnectorNetworkTxnId::new(network_txn_id) + }); + Ok(optional_network_txn_id) + } } impl ConnectorRedirectResponse for Worldpay { diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs index 884caa9e840b..52ae2c4f16f5 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/requests.rs @@ -52,18 +52,22 @@ pub enum TokenCreationType { Worldpay, } +#[serde_with::skip_serializing_none] #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "camelCase")] pub struct CustomerAgreement { #[serde(rename = "type")] pub agreement_type: CustomerAgreementType, - pub stored_card_usage: StoredCardUsageType, + pub stored_card_usage: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub scheme_reference: Option>, } #[derive(Clone, Debug, PartialEq, Serialize)] #[serde(rename_all = "lowercase")] pub enum CustomerAgreementType { Subscription, + Unscheduled, } #[derive(Clone, Debug, PartialEq, Serialize)] @@ -78,6 +82,7 @@ pub enum StoredCardUsageType { pub enum PaymentInstrument { Card(CardPayment), CardToken(CardToken), + RawCardForNTI(RawCardDetails), Googlepay(WalletPayment), Applepay(WalletPayment), } @@ -85,15 +90,22 @@ pub enum PaymentInstrument { #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct CardPayment { - #[serde(rename = "type")] - pub payment_type: PaymentType, + #[serde(flatten)] + pub raw_card_details: RawCardDetails, + pub cvc: Secret, #[serde(skip_serializing_if = "Option::is_none")] pub card_holder_name: Option>, - pub card_number: cards::CardNumber, - pub expiry_date: ExpiryDate, #[serde(skip_serializing_if = "Option::is_none")] pub billing_address: Option, - pub cvc: Secret, +} + +#[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RawCardDetails { + #[serde(rename = "type")] + pub payment_type: PaymentType, + pub card_number: cards::CardNumber, + pub expiry_date: ExpiryDate, } #[derive(Clone, Debug, PartialEq, Default, Serialize, Deserialize)] diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs index 5e9fb0304243..b4d6a3704016 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/response.rs @@ -43,6 +43,8 @@ pub struct AuthorizedResponse { pub fraud: Option, /// Mandate's token pub token: Option, + /// Network transaction ID + pub scheme_reference: Option>, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -429,9 +431,13 @@ pub struct WorldpayWebhookTransactionId { #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EventDetails { - pub transaction_reference: String, #[serde(rename = "type")] pub event_type: EventType, + pub transaction_reference: String, + /// Mandate's token + pub token: Option, + /// Network transaction ID + pub scheme_reference: Option>, } #[derive(Debug, Serialize, Deserialize)] diff --git a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs index ec23b520a200..cd028e81ef1b 100644 --- a/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/worldpay/transformers.rs @@ -76,12 +76,14 @@ fn fetch_payment_instrument( ) -> CustomResult { match payment_method { PaymentMethodData::Card(card) => Ok(PaymentInstrument::Card(CardPayment { - payment_type: PaymentType::Plain, - expiry_date: ExpiryDate { - month: card.get_expiry_month_as_i8()?, - year: card.get_expiry_year_as_4_digit_i32()?, + raw_card_details: RawCardDetails { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: card.get_expiry_month_as_i8()?, + year: card.get_expiry_year_as_4_digit_i32()?, + }, + card_number: card.card_number, }, - card_number: card.card_number, cvc: card.card_cvc, card_holder_name: billing_address.and_then(|address| address.get_optional_full_name()), billing_address: if let Some(address) = @@ -107,6 +109,16 @@ fn fetch_payment_instrument( None }, })), + PaymentMethodData::CardDetailsForNetworkTransactionId(raw_card_details) => { + Ok(PaymentInstrument::RawCardForNTI(RawCardDetails { + payment_type: PaymentType::Plain, + expiry_date: ExpiryDate { + month: raw_card_details.get_expiry_month_as_i8()?, + year: raw_card_details.get_expiry_year_as_4_digit_i32()?, + }, + card_number: raw_card_details.card_number, + })) + } PaymentMethodData::MandatePayment => mandate_ids .and_then(|mandate_ids| { mandate_ids @@ -185,13 +197,10 @@ fn fetch_payment_instrument( | PaymentMethodData::GiftCard(_) | PaymentMethodData::OpenBanking(_) | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("worldpay"), - ) - .into()) - } + | PaymentMethodData::NetworkToken(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("worldpay"), + ) + .into()), } } @@ -439,6 +448,7 @@ fn get_token_and_agreement( payment_method_data: &PaymentMethodData, setup_future_usage: Option, off_session: Option, + mandate_ids: Option, ) -> (Option, Option) { match (payment_method_data, setup_future_usage, off_session) { // CIT @@ -448,7 +458,8 @@ fn get_token_and_agreement( }), Some(CustomerAgreement { agreement_type: CustomerAgreementType::Subscription, - stored_card_usage: StoredCardUsageType::First, + stored_card_usage: Some(StoredCardUsageType::First), + scheme_reference: None, }), ), // MIT @@ -456,7 +467,26 @@ fn get_token_and_agreement( None, Some(CustomerAgreement { agreement_type: CustomerAgreementType::Subscription, - stored_card_usage: StoredCardUsageType::Subsequent, + stored_card_usage: Some(StoredCardUsageType::Subsequent), + scheme_reference: None, + }), + ), + // NTI with raw card data + (PaymentMethodData::CardDetailsForNetworkTransactionId(_), _, _) => ( + None, + mandate_ids.and_then(|mandate_ids| { + mandate_ids + .mandate_reference_id + .and_then(|mandate_id| match mandate_id { + MandateReferenceId::NetworkMandateId(network_transaction_id) => { + Some(CustomerAgreement { + agreement_type: CustomerAgreementType::Unscheduled, + scheme_reference: Some(network_transaction_id.into()), + stored_card_usage: None, + }) + } + _ => None, + }) }), ), _ => (None, None), @@ -487,6 +517,7 @@ impl TryFrom<(&WorldpayRouterData<&T>, &Secret ), ) -> Result { let (router_data, optional_correlation_id) = item; - let (description, redirection_data, mandate_reference, error) = router_data + let (description, redirection_data, mandate_reference, network_txn_id, error) = router_data .response .other_fields .as_ref() @@ -660,6 +691,7 @@ impl mandate_metadata: None, connector_mandate_request_reference_id: None, }), + res.scheme_reference.clone(), None, ), WorldpayPaymentResponseFields::DDCResponse(res) => ( @@ -681,6 +713,7 @@ impl }), None, None, + None, ), WorldpayPaymentResponseFields::ThreeDsChallenged(res) => ( None, @@ -694,16 +727,18 @@ impl }), None, None, + None, ), WorldpayPaymentResponseFields::RefusedResponse(res) => ( None, None, None, + None, Some((res.refusal_code.clone(), res.refusal_description.clone())), ), - WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None), + WorldpayPaymentResponseFields::FraudHighRisk(_) => (None, None, None, None, None), }) - .unwrap_or((None, None, None, None)); + .unwrap_or((None, None, None, None, None)); let worldpay_status = router_data.response.outcome.clone(); let optional_error_message = match worldpay_status { PaymentOutcome::ThreeDsAuthenticationFailed => { @@ -725,7 +760,7 @@ impl redirection_data: Box::new(redirection_data), mandate_reference: Box::new(mandate_reference), connector_metadata: None, - network_txn_id: None, + network_txn_id: network_txn_id.map(|id| id.expose()), connector_response_reference_id: optional_correlation_id.clone(), incremental_authorization_allowed: None, charge_id: None, diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 9061a758beab..16d0d748aa91 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -17,7 +17,7 @@ use common_utils::{ use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ address::{Address, AddressDetails, PhoneDetails}, - payment_method_data::{self, Card, PaymentMethodData}, + payment_method_data::{self, Card, CardDetailsForNetworkTransactionId, PaymentMethodData}, router_data::{ ApplePayPredecryptData, ErrorResponse, PaymentMethodToken, RecurringMandatePaymentData, }, @@ -1034,6 +1034,92 @@ impl CardData for Card { } } +impl CardData for CardDetailsForNetworkTransactionId { + fn get_card_expiry_year_2_digit(&self) -> Result, errors::ConnectorError> { + let binding = self.card_exp_year.clone(); + let year = binding.peek(); + Ok(Secret::new( + year.get(year.len() - 2..) + .ok_or(errors::ConnectorError::RequestEncodingFailed)? + .to_string(), + )) + } + fn get_card_issuer(&self) -> Result { + get_card_issuer(self.card_number.peek()) + } + fn get_card_expiry_month_year_2_digit_with_delimiter( + &self, + delimiter: String, + ) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?; + Ok(Secret::new(format!( + "{}{}{}", + self.card_exp_month.peek(), + delimiter, + year.peek() + ))) + } + fn get_expiry_date_as_yyyymm(&self, delimiter: &str) -> Secret { + let year = self.get_expiry_year_4_digit(); + Secret::new(format!( + "{}{}{}", + year.peek(), + delimiter, + self.card_exp_month.peek() + )) + } + fn get_expiry_date_as_mmyyyy(&self, delimiter: &str) -> Secret { + let year = self.get_expiry_year_4_digit(); + Secret::new(format!( + "{}{}{}", + self.card_exp_month.peek(), + delimiter, + year.peek() + )) + } + fn get_expiry_year_4_digit(&self) -> Secret { + let mut year = self.card_exp_year.peek().clone(); + if year.len() == 2 { + year = format!("20{}", year); + } + Secret::new(year) + } + fn get_expiry_date_as_yymm(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); + let month = self.card_exp_month.clone().expose(); + Ok(Secret::new(format!("{year}{month}"))) + } + fn get_expiry_date_as_mmyy(&self) -> Result, errors::ConnectorError> { + let year = self.get_card_expiry_year_2_digit()?.expose(); + let month = self.card_exp_month.clone().expose(); + Ok(Secret::new(format!("{month}{year}"))) + } + fn get_expiry_month_as_i8(&self) -> Result, Error> { + self.card_exp_month + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } + fn get_expiry_year_as_i32(&self) -> Result, Error> { + self.card_exp_year + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } + fn get_expiry_year_as_4_digit_i32(&self) -> Result, Error> { + self.get_expiry_year_4_digit() + .peek() + .clone() + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed) + .map(Secret::new) + } +} + #[track_caller] fn get_card_issuer(card_number: &str) -> Result { for (k, v) in CARD_REGEX.iter() { diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 64d0526d1476..addad75cee0b 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -370,7 +370,7 @@ card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"} [network_transaction_id_supported_connectors] -connector_list = "stripe,adyen,cybersource" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [analytics] source = "sqlx" From af9f2911d0d667bd610cbe1229f3d0be8ec51790 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 26 Dec 2024 16:52:01 +0530 Subject: [PATCH 2/9] chore: format --- .../src/connectors/novalnet/transformers.rs | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index c59837f6f70c..3ce79f5ada6c 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -425,11 +425,12 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym network_transaction_id, )) => match item.router_data.request.payment_method_data { PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { - let novalnet_card = NovalNetPaymentData::RawCardForNTI(NovalnetRawCardDetails { - card_number: raw_card_details.card_number.clone(), - card_expiry_month: raw_card_details.card_exp_month.clone(), - card_expiry_year: raw_card_details.card_exp_year.clone(), - }); + let novalnet_card = + NovalNetPaymentData::RawCardForNTI(NovalnetRawCardDetails { + card_number: raw_card_details.card_number.clone(), + card_expiry_month: raw_card_details.card_exp_month.clone(), + card_expiry_year: raw_card_details.card_exp_year.clone(), + }); let transaction = NovalnetPaymentsRequestTransaction { test_mode, From befc6eeccff7501d9e4ddea06484fa7ec8b5be90 Mon Sep 17 00:00:00 2001 From: Kashif Date: Fri, 10 Jan 2025 15:25:37 +0530 Subject: [PATCH 3/9] chore: remove fiuu from network_transaction_id_supported_connectors --- config/config.example.toml | 2 +- config/deployments/integration_test.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- loadtest/config/development.toml | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index f5cb5683e194..4bff8f412c65 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -803,7 +803,7 @@ check_token_status_url= "" # base url to check token status from token servic connector_list = "cybersource" # Supported connectors for network tokenization [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" # Supported connectors for network transaction id +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" # Supported connectors for network transaction id [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 2a2d1413ed22..b6e487e5a3b2 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [payouts] diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index afcaf9a42344..7242f263672e 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [payouts] diff --git a/config/development.toml b/config/development.toml index d1d4d10eeb13..9af3a294d6d9 100644 --- a/config/development.toml +++ b/config/development.toml @@ -654,7 +654,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ecf2dd30d370..28dbdd37c7ab 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -540,7 +540,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index a41dcca70876..640a60ae40d4 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -370,7 +370,7 @@ card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"} [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [analytics] source = "sqlx" From f8517e4114042aa37b0adf621dd4be22f65e84d0 Mon Sep 17 00:00:00 2001 From: Kashif Date: Mon, 13 Jan 2025 14:10:14 +0530 Subject: [PATCH 4/9] chore: update code after merging w main --- .../src/connectors/novalnet/transformers.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs index 8bcdb6c9da5d..6d3199ecf1a7 100644 --- a/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/novalnet/transformers.rs @@ -453,7 +453,7 @@ impl TryFrom<&NovalnetRouterData<&PaymentsAuthorizeRouterData>> for NovalnetPaym let transaction = NovalnetPaymentsRequestTransaction { test_mode, payment_type: NovalNetPaymentTypes::CREDITCARD, - amount: item.amount.clone(), + amount: NovalNetAmount::StringMinor(item.amount.clone()), currency: item.router_data.request.currency, order_no: item.router_data.connector_request_reference_id.clone(), hook_url: Some(hook_url), @@ -1518,6 +1518,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { payment_data: Some(novalnet_card), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -1547,6 +1548,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { payment_data: Some(novalnet_google_pay), enforce_3d, create_token, + scheme_tid: None, }; Ok(Self { @@ -1571,6 +1573,7 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { })), enforce_3d: None, create_token, + scheme_tid: None, }; Ok(Self { @@ -1611,7 +1614,9 @@ impl TryFrom<&SetupMandateRouterData> for NovalnetPaymentsRequest { payment_data: None, enforce_3d: None, create_token, + scheme_tid: None, }; + Ok(Self { merchant, transaction, From 11ab30efda2c7d3f3c798c8290251d4cda273608 Mon Sep 17 00:00:00 2001 From: Kashif Date: Tue, 7 Jan 2025 11:04:15 +0530 Subject: [PATCH 5/9] chore: fiuu NTID --- config/config.example.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- .../src/connectors/fiuu.rs | 49 ++-- .../src/connectors/fiuu/transformers.rs | 261 +++++++++++------- crates/router/locales/en.yml | 2 +- .../payment_connector_required_fields.rs | 2 + loadtest/config/development.toml | 2 +- 9 files changed, 198 insertions(+), 126 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index 4648babdff1d..20882c0bf659 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -803,7 +803,7 @@ check_token_status_url= "" # base url to check token status from token servic connector_list = "cybersource" # Supported connectors for network tokenization [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,novalnet,stripe,worldpay" # Supported connectors for network transaction id +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" # Supported connectors for network transaction id [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 7242f263672e..afcaf9a42344 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [payouts] diff --git a/config/development.toml b/config/development.toml index bbbec322fe59..188f8470a58a 100644 --- a/config/development.toml +++ b/config/development.toml @@ -654,7 +654,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index cfdf4b0e0c20..420ea1f9c22f 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -540,7 +540,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu.rs b/crates/hyperswitch_connectors/src/connectors/fiuu.rs index 3ea8793d9f59..70bca41582c5 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu.rs @@ -254,16 +254,23 @@ impl ConnectorIntegration CustomResult { - let url = if req.request.off_session == Some(true) { - format!( + let optional_is_mit_flow = req.request.off_session; + let optional_is_nti_flow = req + .request + .mandate_id + .as_ref() + .map(|mandate_id| mandate_id.is_network_transaction_id_flow()); + let url = match (optional_is_mit_flow, optional_is_nti_flow) { + (Some(true), Some(false)) => 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) - ) + ), + _ => { + format!( + "{}RMS/API/Direct/1.4.0/index.php", + self.base_url(connectors) + ) + } }; Ok(url) } @@ -280,14 +287,24 @@ impl ConnectorIntegration { + let recurring_request = fiuu::FiuuMandateRequest::try_from(&connector_router_data)?; + build_form_from_struct(recurring_request) + .change_context(errors::ConnectorError::ParsingFailed)? + } + _ => { + let payment_request = fiuu::FiuuPaymentRequest::try_from(&connector_router_data)?; + build_form_from_struct(payment_request) + .change_context(errors::ConnectorError::ParsingFailed)? + } }; Ok(RequestContent::FormData(connector_req)) } diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index e32168d0d78b..55c5b1ff30f9 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -14,8 +14,8 @@ use common_utils::{ use error_stack::{Report, ResultExt}; use hyperswitch_domain_models::{ payment_method_data::{ - BankRedirectData, Card, GooglePayWalletData, PaymentMethodData, RealTimePaymentData, - WalletData, + BankRedirectData, Card, CardDetailsForNetworkTransactionId, GooglePayWalletData, + PaymentMethodData, RealTimePaymentData, WalletData, }, router_data::{ ApplePayPredecryptData, ConnectorAuthType, ErrorResponse, PaymentMethodToken, RouterData, @@ -287,6 +287,7 @@ pub struct FiuuPaymentRequest { pub enum FiuuPaymentMethodData { FiuuQRData(Box), FiuuCardData(Box), + FiuuCardWithNTI(Box), FiuuFpxData(Box), FiuuGooglePayData(Box), FiuuApplePayData(Box), @@ -322,6 +323,18 @@ pub struct FiuuCardData { customer_email: Option, } +#[derive(Serialize, Debug, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub struct FiuuCardWithNTI { + #[serde(rename = "TxnChannel")] + txn_channel: TxnChannel, + cc_pan: CardNumber, + cc_month: Secret, + cc_year: Secret, + #[serde(rename = "OriginalSchemeID")] + original_scheme_id: Secret, +} + #[derive(Serialize, Debug, Clone)] #[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub struct FiuuApplePayData { @@ -412,125 +425,150 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque 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)) - } - PaymentMethodData::RealTimePayment(ref real_time_payment_data) => { - match *real_time_payment_data.clone() { - RealTimePaymentData::DuitNow {} => { - Ok(FiuuPaymentMethodData::FiuuQRData(Box::new(FiuuQRData { - txn_channel: TxnChannel::RppDuitNowQr, + let payment_method_data = match item + .router_data + .request + .mandate_id + .clone() + .and_then(|mandate_id| mandate_id.mandate_reference_id) + { + None => match item.router_data.request.payment_method_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 {} => { + Ok(FiuuPaymentMethodData::FiuuQRData(Box::new(FiuuQRData { + txn_channel: TxnChannel::RppDuitNowQr, + }))) + } + RealTimePaymentData::Fps {} + | RealTimePaymentData::PromptPay {} + | RealTimePaymentData::VietQr {} => { + Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()) + } + } + } + PaymentMethodData::BankRedirect(ref bank_redirect_data) => match bank_redirect_data + { + BankRedirectData::OnlineBankingFpx { ref issuer } => { + Ok(FiuuPaymentMethodData::FiuuFpxData(Box::new(FiuuFPXData { + txn_channel: FPXTxnChannel::try_from(*issuer)?, + non_3ds, }))) } - RealTimePaymentData::Fps {} - | RealTimePaymentData::PromptPay {} - | RealTimePaymentData::VietQr {} => { + BankRedirectData::BancontactCard { .. } + | BankRedirectData::Bizum {} + | BankRedirectData::Blik { .. } + | BankRedirectData::Eps { .. } + | BankRedirectData::Giropay { .. } + | BankRedirectData::Ideal { .. } + | BankRedirectData::Interac { .. } + | BankRedirectData::OnlineBankingCzechRepublic { .. } + | BankRedirectData::OnlineBankingFinland { .. } + | BankRedirectData::OnlineBankingPoland { .. } + | BankRedirectData::OnlineBankingSlovakia { .. } + | BankRedirectData::OpenBankingUk { .. } + | BankRedirectData::Przelewy24 { .. } + | BankRedirectData::Sofort { .. } + | BankRedirectData::Trustly { .. } + | BankRedirectData::OnlineBankingThailand { .. } + | BankRedirectData::LocalBankRedirect {} => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), ) .into()) } - } - } - PaymentMethodData::BankRedirect(ref bank_redirect_data) => match bank_redirect_data { - BankRedirectData::OnlineBankingFpx { ref issuer } => { - Ok(FiuuPaymentMethodData::FiuuFpxData(Box::new(FiuuFPXData { - txn_channel: FPXTxnChannel::try_from(*issuer)?, - non_3ds, - }))) - } - BankRedirectData::BancontactCard { .. } - | BankRedirectData::Bizum {} - | BankRedirectData::Blik { .. } - | BankRedirectData::Eps { .. } - | BankRedirectData::Giropay { .. } - | BankRedirectData::Ideal { .. } - | BankRedirectData::Interac { .. } - | BankRedirectData::OnlineBankingCzechRepublic { .. } - | BankRedirectData::OnlineBankingFinland { .. } - | BankRedirectData::OnlineBankingPoland { .. } - | BankRedirectData::OnlineBankingSlovakia { .. } - | BankRedirectData::OpenBankingUk { .. } - | BankRedirectData::Przelewy24 { .. } - | BankRedirectData::Sofort { .. } - | BankRedirectData::Trustly { .. } - | BankRedirectData::OnlineBankingThailand { .. } - | BankRedirectData::LocalBankRedirect {} => { + }, + PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { + WalletData::GooglePay(google_pay_data) => { + FiuuPaymentMethodData::try_from(google_pay_data) + } + WalletData::ApplePay(_apple_pay_data) => { + let payment_method_token = item.router_data.get_payment_method_token()?; + match payment_method_token { + PaymentMethodToken::Token(_) => { + Err(unimplemented_payment_method!("Apple Pay", "Manual", "Fiuu"))? + } + PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { + FiuuPaymentMethodData::try_from(decrypt_data) + } + PaymentMethodToken::PazeDecrypt(_) => { + Err(unimplemented_payment_method!("Paze", "Fiuu"))? + } + } + } + WalletData::AliPayQr(_) + | WalletData::AliPayRedirect(_) + | WalletData::AliPayHkRedirect(_) + | WalletData::MomoRedirect(_) + | WalletData::KakaoPayRedirect(_) + | WalletData::GoPayRedirect(_) + | WalletData::GcashRedirect(_) + | WalletData::ApplePayRedirect(_) + | WalletData::ApplePayThirdPartySdk(_) + | WalletData::DanaRedirect {} + | WalletData::GooglePayRedirect(_) + | WalletData::GooglePayThirdPartySdk(_) + | WalletData::MbWayRedirect(_) + | WalletData::MobilePayRedirect(_) + | WalletData::PaypalRedirect(_) + | WalletData::PaypalSdk(_) + | WalletData::Paze(_) + | WalletData::SamsungPay(_) + | WalletData::TwintRedirect {} + | WalletData::VippsRedirect {} + | WalletData::TouchNGoRedirect(_) + | WalletData::WeChatPayRedirect(_) + | WalletData::WeChatPayQr(_) + | WalletData::CashappQr(_) + | WalletData::SwishQr(_) + | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), + }, + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Reward + | PaymentMethodData::Upi(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::NetworkToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), ) .into()) } }, - PaymentMethodData::Wallet(ref wallet_data) => match wallet_data { - WalletData::GooglePay(google_pay_data) => { - FiuuPaymentMethodData::try_from(google_pay_data) + // Card payments using network transaction ID + Some(payments::MandateReferenceId::NetworkMandateId( + network_transaction_id, + )) => match item.router_data.request.payment_method_data { + PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { + FiuuPaymentMethodData::try_from((raw_card_details, network_transaction_id)) } - WalletData::ApplePay(_apple_pay_data) => { - let payment_method_token = item.router_data.get_payment_method_token()?; - match payment_method_token { - PaymentMethodToken::Token(_) => { - Err(unimplemented_payment_method!("Apple Pay", "Manual", "Fiuu"))? - } - PaymentMethodToken::ApplePayDecrypt(decrypt_data) => { - FiuuPaymentMethodData::try_from(decrypt_data) - } - PaymentMethodToken::PazeDecrypt(_) => { - Err(unimplemented_payment_method!("Paze", "Fiuu"))? - } - } - } - WalletData::AliPayQr(_) - | WalletData::AliPayRedirect(_) - | WalletData::AliPayHkRedirect(_) - | WalletData::MomoRedirect(_) - | WalletData::KakaoPayRedirect(_) - | WalletData::GoPayRedirect(_) - | WalletData::GcashRedirect(_) - | WalletData::ApplePayRedirect(_) - | WalletData::ApplePayThirdPartySdk(_) - | WalletData::DanaRedirect {} - | WalletData::GooglePayRedirect(_) - | WalletData::GooglePayThirdPartySdk(_) - | WalletData::MbWayRedirect(_) - | WalletData::MobilePayRedirect(_) - | WalletData::PaypalRedirect(_) - | WalletData::PaypalSdk(_) - | WalletData::Paze(_) - | WalletData::SamsungPay(_) - | WalletData::TwintRedirect {} - | WalletData::VippsRedirect {} - | WalletData::TouchNGoRedirect(_) - | WalletData::WeChatPayRedirect(_) - | WalletData::WeChatPayQr(_) - | WalletData::CashappQr(_) - | WalletData::SwishQr(_) - | WalletData::Mifinity(_) => Err(errors::ConnectorError::NotImplemented( + _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), ) .into()), }, - PaymentMethodData::CardRedirect(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::MobilePayment(_) - | PaymentMethodData::Reward - | PaymentMethodData::Upi(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::OpenBanking(_) - | PaymentMethodData::NetworkToken(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { - Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("fiuu"), - ) - .into()) - } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), }?; Ok(Self { @@ -556,7 +594,7 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { if item.request.is_customer_initiated_mandate_payment() { (Some(1), Some(item.get_billing_email()?)) } else { - (None, None) + (Some(3), None) }; let non_3ds = match item.is_three_ds() { false => 1, @@ -575,6 +613,21 @@ impl TryFrom<(&Card, &PaymentsAuthorizeRouterData)> for FiuuPaymentMethodData { } } +impl TryFrom<(&CardDetailsForNetworkTransactionId, String)> for FiuuPaymentMethodData { + type Error = Report; + fn try_from( + (raw_card_data, network_transaction_id): (&CardDetailsForNetworkTransactionId, String), + ) -> Result { + Ok(Self::FiuuCardWithNTI(Box::new(FiuuCardWithNTI { + txn_channel: TxnChannel::Creditan, + cc_pan: raw_card_data.card_number.clone(), + cc_month: raw_card_data.card_exp_month.clone(), + cc_year: raw_card_data.card_exp_year.clone(), + original_scheme_id: Secret::new(network_transaction_id), + }))) + } +} + impl TryFrom<&GooglePayWalletData> for FiuuPaymentMethodData { type Error = Report; fn try_from(data: &GooglePayWalletData) -> Result { diff --git a/crates/router/locales/en.yml b/crates/router/locales/en.yml index c85be7ccf466..67eec844fb10 100644 --- a/crates/router/locales/en.yml +++ b/crates/router/locales/en.yml @@ -7,7 +7,7 @@ payout_link: status: title: "Payout Status" info: - ref_id: "Ref Id" + ref_id: "Reference Id" error_code: "Error Code" error_message: "Error Message" message: diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 4f6327d74b7a..40bb51200873 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -67,6 +67,7 @@ impl Default for Mandates { enums::Connector::Authorizedotnet, enums::Connector::Globalpay, enums::Connector::Worldpay, + enums::Connector::Fiuu, enums::Connector::Multisafepay, enums::Connector::Nexinets, enums::Connector::Noon, @@ -88,6 +89,7 @@ impl Default for Mandates { enums::Connector::Authorizedotnet, enums::Connector::Globalpay, enums::Connector::Worldpay, + enums::Connector::Fiuu, enums::Connector::Multisafepay, enums::Connector::Nexinets, enums::Connector::Noon, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index e90eb16ab619..0bce9bf1bada 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -370,7 +370,7 @@ card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"} [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" [analytics] source = "sqlx" From ed2c5621da6173acf4224735541461d55cf98175 Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 15 Jan 2025 22:04:19 +0530 Subject: [PATCH 6/9] chore: update code after merging with main --- crates/hyperswitch_connectors/src/utils.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index b1369ad003a3..671aed363780 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -1124,6 +1124,11 @@ impl CardData for CardDetailsForNetworkTransactionId { .change_context(errors::ConnectorError::ResponseDeserializationFailed) .map(Secret::new) } + fn get_cardholder_name(&self) -> Result, Error> { + self.card_holder_name + .clone() + .ok_or_else(missing_field_err("card.card_holder_name")) + } } #[track_caller] From f43fd4f995c44e51ec8a656d4ffd7c50a4d01f5c Mon Sep 17 00:00:00 2001 From: Kashif Date: Wed, 15 Jan 2025 23:32:10 +0530 Subject: [PATCH 7/9] chore: add test cases for NTID for novalnet and worldpay --- .../cypress/e2e/PaymentUtils/Fiuu.js | 15 ++++ .../cypress/e2e/PaymentUtils/Novalnet.js | 59 +++++++++++--- .../cypress/e2e/PaymentUtils/WorldPay.js | 77 +++++++++++++++++-- 3 files changed, 135 insertions(+), 16 deletions(-) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js index 996356117ab0..a707643248e3 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Fiuu.js @@ -382,6 +382,21 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + amount: 6500, + authentication_type: "no_three_ds", + currency: "USD", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, PaymentMethodIdMandateNo3DSAutoCapture: { Request: { payment_method: "card", diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js index 049997c541c3..1025b474114c 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Novalnet.js @@ -7,22 +7,24 @@ const successfulThreeDSTestCardDetails = { }; const successfulNo3DSCardDetails = { - card_number: "4242424242424242", - card_exp_month: "01", - card_exp_year: "50", + card_number: "4200000000000000", + card_exp_month: "03", + card_exp_year: "30", 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", - }, +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, mandate_type: { single_use: { amount: 8000, @@ -260,6 +262,41 @@ export const connectorDetails = { }, }, }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, ZeroAuthPaymentIntent: { Request: { amount: 0, diff --git a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js index a58bbb22f81d..a811cac786e7 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/WorldPay.js @@ -56,14 +56,14 @@ const paymentMethodDataNoThreeDsResponse = { card_extended_bin: null, card_exp_month: "10", card_exp_year: "30", - card_holder_name: null, + card_holder_name: "morino", payment_checks: null, authentication_data: null, }, billing: null, }; -const payment_method_data_3ds = { +const paymentMethodDataThreeDsResponse = { card: { last4: "1091", card_type: "CREDIT", @@ -74,7 +74,7 @@ const payment_method_data_3ds = { card_extended_bin: null, card_exp_month: "10", card_exp_year: "30", - card_holder_name: null, + card_holder_name: "morino", payment_checks: null, authentication_data: null, }, @@ -328,7 +328,7 @@ export const connectorDetails = { body: { status: "requires_customer_action", setup_future_usage: "on_session", - payment_method_data: payment_method_data_3ds, + payment_method_data: paymentMethodDataThreeDsResponse, }, }, }, @@ -349,7 +349,7 @@ export const connectorDetails = { body: { status: "requires_customer_action", setup_future_usage: "on_session", - payment_method_data: payment_method_data_3ds, + payment_method_data: paymentMethodDataThreeDsResponse, }, }, }, @@ -512,6 +512,73 @@ export const connectorDetails = { }, }, }, + PaymentIntentOffSession: { + Request: { + amount: 6500, + authentication_type: "no_three_ds", + currency: "USD", + customer_acceptance: null, + setup_future_usage: "off_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + MITAutoCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + MITManualCapture: { + Request: {}, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + shipping_cost: 50, + amount: 6500, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNoThreeDsCardDetailsRequest, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 6550, + amount: 6500, + net_amount: 6550, + }, + }, + }, ZeroAuthMandate: { Request: { payment_method: "card", From fe29efdb93056d131d28134802142412e212e3af Mon Sep 17 00:00:00 2001 From: "hyperswitch-bot[bot]" <148525504+hyperswitch-bot[bot]@users.noreply.github.com> Date: Wed, 15 Jan 2025 18:45:19 +0000 Subject: [PATCH 8/9] chore: run formatter --- .../src/connectors/fiuu/transformers.rs | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs index 55c5b1ff30f9..51fc23eee41d 100644 --- a/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/fiuu/transformers.rs @@ -554,17 +554,17 @@ impl TryFrom<&FiuuRouterData<&PaymentsAuthorizeRouterData>> for FiuuPaymentReque } }, // Card payments using network transaction ID - Some(payments::MandateReferenceId::NetworkMandateId( - network_transaction_id, - )) => match item.router_data.request.payment_method_data { - PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { - FiuuPaymentMethodData::try_from((raw_card_details, network_transaction_id)) + Some(payments::MandateReferenceId::NetworkMandateId(network_transaction_id)) => { + match item.router_data.request.payment_method_data { + PaymentMethodData::CardDetailsForNetworkTransactionId(ref raw_card_details) => { + FiuuPaymentMethodData::try_from((raw_card_details, network_transaction_id)) + } + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("fiuu"), + ) + .into()), } - _ => Err(errors::ConnectorError::NotImplemented( - utils::get_unimplemented_payment_method_error_message("fiuu"), - ) - .into()), - }, + } _ => Err(errors::ConnectorError::NotImplemented( utils::get_unimplemented_payment_method_error_message("fiuu"), ) From 768bc70c8013f4ecc9d0a73493dde668e6460317 Mon Sep 17 00:00:00 2001 From: Kashif Date: Thu, 16 Jan 2025 00:29:22 +0530 Subject: [PATCH 9/9] chore: remove fiuu from network_transaction_id_supported_connectors list --- config/config.example.toml | 2 +- config/deployments/sandbox.toml | 2 +- config/development.toml | 2 +- config/docker_compose.toml | 2 +- loadtest/config/development.toml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config/config.example.toml b/config/config.example.toml index f5399c467ffd..c860420da38a 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -802,7 +802,7 @@ check_token_status_url= "" # base url to check token status from token servic connector_list = "cybersource" # Supported connectors for network tokenization [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" # Supported connectors for network transaction id +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" # Supported connectors for network transaction id [grpc_client.dynamic_routing_client] # Dynamic Routing Client Configuration host = "localhost" # Client Host diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index afcaf9a42344..7242f263672e 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -191,7 +191,7 @@ card.credit = { connector_list = "cybersource" } # Update Mandate sup card.debit = { connector_list = "cybersource" } # Update Mandate supported payment method type and connector for card [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [payouts] diff --git a/config/development.toml b/config/development.toml index a91d3af289d1..a32102aeebfe 100644 --- a/config/development.toml +++ b/config/development.toml @@ -653,7 +653,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_request_reference_id_config] merchant_ids_send_payment_id_as_connector_request_id = [] diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 420ea1f9c22f..cfdf4b0e0c20 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -540,7 +540,7 @@ card.credit = { connector_list = "cybersource" } card.debit = { connector_list = "cybersource" } [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [connector_customer] connector_list = "gocardless,stax,stripe" diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 0bce9bf1bada..e90eb16ab619 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -370,7 +370,7 @@ card.credit ={connector_list ="cybersource"} card.debit = {connector_list ="cybersource"} [network_transaction_id_supported_connectors] -connector_list = "adyen,cybersource,fiuu,novalnet,stripe,worldpay" +connector_list = "adyen,cybersource,novalnet,stripe,worldpay" [analytics] source = "sqlx"