diff --git a/crates/hyperswitch_connectors/src/connectors/nexixpay.rs b/crates/hyperswitch_connectors/src/connectors/nexixpay.rs index 76085324167e..701a0a54e4b4 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexixpay.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexixpay.rs @@ -1,4 +1,5 @@ pub mod transformers; +use std::collections::HashSet; use common_enums::enums; use common_utils::{ @@ -9,6 +10,7 @@ use common_utils::{ }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, router_flow_types::{ access_token_auth::AccessTokenAuth, @@ -46,7 +48,7 @@ use uuid::Uuid; use crate::{ constants::headers, types::ResponseRouterData, - utils::{self, RefundsRequestData}, + utils::{self, PaymentMethodDataType, RefundsRequestData}, }; #[derive(Clone)] @@ -212,6 +214,15 @@ impl ConnectorValidation for Nexixpay { ), } } + 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 Nexixpay {} @@ -415,10 +426,14 @@ impl ConnectorIntegration CustomResult { - Ok(format!("{}/orders/3steps/init", self.base_url(connectors))) + if req.request.off_session == Some(true) { + Ok(format!("{}/orders/mit", self.base_url(connectors))) + } else { + Ok(format!("{}/orders/3steps/init", self.base_url(connectors))) + } } fn get_request_body( diff --git a/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs b/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs index b1e9c3015e8b..9c35b913db72 100644 --- a/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/nexixpay/transformers.rs @@ -14,7 +14,9 @@ use hyperswitch_domain_models::{ CompleteAuthorizeData, PaymentsAuthorizeData, PaymentsCancelData, PaymentsCaptureData, PaymentsPreProcessingData, PaymentsSyncData, ResponseId, }, - router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, + router_response_types::{ + MandateReference, PaymentsResponseData, RedirectForm, RefundsResponseData, + }, types::{ PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, PaymentsCompleteAuthorizeRouterData, PaymentsPreProcessingRouterData, RefundsRouterData, @@ -47,11 +49,58 @@ impl From<(StringMinorUnit, T)> for NexixpayRouterData { } } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum NexixpayRecurringAction { + NoRecurring, + SubsequentPayment, + ContractCreation, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum ContractType { + MitUnscheduled, + MitScheduled, + Cit, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct RecurrenceRequest { + action: NexixpayRecurringAction, + contract_id: Secret, + contract_type: ContractType, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayNonMandatePaymentRequest { + card: NexixpayCard, + recurrence: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayMandatePaymentRequest { + contract_id: Secret, + capture_type: Option, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum NexixpayPaymentsRequestData { + NexixpayNonMandatePaymentRequest(Box), + NexixpayMandatePaymentRequest(Box), +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NexixpayPaymentsRequest { order: Order, - card: NexixpayCard, + #[serde(flatten)] + payment_data: NexixpayPaymentsRequestData, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -69,6 +118,7 @@ pub struct NexixpayCompleteAuthorizeRequest { operation_id: String, capture_type: Option, three_d_s_auth_data: ThreeDSAuthData, + recurrence: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -108,13 +158,13 @@ pub struct Order { #[serde(rename_all = "camelCase")] pub struct CustomerInfo { card_holder_name: Secret, - billing_address: Address, - shipping_address: Option
, + billing_address: BillingAddress, + shipping_address: Option, } #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct Address { +pub struct BillingAddress { name: Secret, street: Secret, city: String, @@ -122,6 +172,16 @@ pub struct Address { country: enums::CountryAlpha2, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ShippingAddress { + name: Option>, + street: Option>, + city: Option, + post_code: Option>, + country: Option, +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct NexixpayCard { @@ -137,12 +197,26 @@ struct Recurrence { #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] -pub struct NexixpayPaymentsResponse { +pub struct PaymentsResponse { operation: Operation, three_d_s_auth_request: String, three_d_s_auth_url: Secret, } +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct NexixpayMandateResponse { + operation: Operation, +} + +#[derive(Debug, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +#[serde(untagged)] +pub enum NexixpayPaymentsResponse { + PaymentResponse(Box), + MandateResponse(Box), +} + #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct ThreeDSAuthResult { @@ -371,54 +445,158 @@ impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaym fn try_from( item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { - match item.router_data.request.payment_method_data { - PaymentMethodData::Card(ref req_card) => { - let card = NexixpayCard { - pan: req_card.card_number.clone(), - expiry_date: req_card.get_expiry_date_as_mmyy()?, - }; - let billing_address = Address { - name: item.router_data.get_billing_full_name()?, - street: item.router_data.get_billing_line1()?, - city: item.router_data.get_billing_city()?, - post_code: item.router_data.get_billing_zip()?, - country: item.router_data.get_billing_country()?, - }; - let customer_info = CustomerInfo { - card_holder_name: item.router_data.get_billing_full_name()?, - billing_address: billing_address.clone(), - shipping_address: Some(billing_address), - }; - let order = Order { - order_id: item.router_data.connector_request_reference_id.clone(), - amount: item.amount.clone(), - currency: item.router_data.request.currency, - description: item.router_data.description.clone(), - customer_info, + let billing_address_street = format!( + "{}, {}", + item.router_data.get_billing_line1()?.expose(), + item.router_data.get_billing_line2()?.expose() + ); + + let billing_address = BillingAddress { + name: item.router_data.get_billing_full_name()?, + street: Secret::new(billing_address_street), + city: item.router_data.get_billing_city()?, + post_code: item.router_data.get_billing_zip()?, + country: item.router_data.get_billing_country()?, + }; + let shipping_address_street = match ( + item.router_data.get_optional_shipping_line1(), + item.router_data.get_optional_shipping_line2(), + ) { + (Some(line1), Some(line2)) => Some(Secret::new(format!( + "{}, {}", + line1.expose(), + line2.expose() + ))), + (Some(line1), None) => Some(Secret::new(line1.expose())), + (None, Some(line2)) => Some(Secret::new(line2.expose())), + (None, None) => None, + }; + + let shipping_address = item + .router_data + .get_optional_billing() + .map(|_| ShippingAddress { + name: item.router_data.get_optional_shipping_full_name(), + street: shipping_address_street, + city: item.router_data.get_optional_shipping_city(), + post_code: item.router_data.get_optional_shipping_zip(), + country: item.router_data.get_optional_shipping_country(), + }); + let customer_info = CustomerInfo { + card_holder_name: item.router_data.get_billing_full_name()?, + billing_address: billing_address.clone(), + shipping_address: shipping_address.clone(), + }; + let order = Order { + order_id: item.router_data.connector_request_reference_id.clone(), + amount: item.amount.clone(), + currency: item.router_data.request.currency, + description: item.router_data.description.clone(), + customer_info, + }; + let payment_data = NexixpayPaymentsRequestData::try_from(item)?; + Ok(Self { + order, + payment_data, + }) + } +} + +impl TryFrom<&NexixpayRouterData<&PaymentsAuthorizeRouterData>> for NexixpayPaymentsRequestData { + type Error = error_stack::Report; + fn try_from( + item: &NexixpayRouterData<&PaymentsAuthorizeRouterData>, + ) -> Result { + match item + .router_data + .request + .mandate_id + .clone() + .and_then(|mandate_id| mandate_id.mandate_reference_id) + { + None => { + let recurrence_request_obj = if item.router_data.request.is_mandate_payment() { + let contract_id = item + .router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + })?; + Some(RecurrenceRequest { + action: NexixpayRecurringAction::ContractCreation, + contract_id: Secret::new(contract_id), + contract_type: ContractType::MitUnscheduled, + }) + } else { + None }; - Ok(Self { order, card }) + + match item.router_data.request.payment_method_data { + PaymentMethodData::Card(ref req_card) => { + if item.router_data.is_three_ds() { + Ok(Self::NexixpayNonMandatePaymentRequest(Box::new( + NexixpayNonMandatePaymentRequest { + card: NexixpayCard { + pan: req_card.card_number.clone(), + expiry_date: req_card.get_expiry_date_as_mmyy()?, + }, + recurrence: recurrence_request_obj, + }, + ))) + } else { + Err(errors::ConnectorError::NotSupported { + message: "No threeds is not supported".to_string(), + connector: "nexixpay", + } + .into()) + } + } + PaymentMethodData::CardRedirect(_) + | PaymentMethodData::Wallet(_) + | PaymentMethodData::PayLater(_) + | PaymentMethodData::BankRedirect(_) + | PaymentMethodData::BankDebit(_) + | PaymentMethodData::BankTransfer(_) + | PaymentMethodData::Crypto(_) + | PaymentMethodData::MandatePayment + | PaymentMethodData::Reward + | PaymentMethodData::RealTimePayment(_) + | PaymentMethodData::Upi(_) + | PaymentMethodData::MobilePayment(_) + | PaymentMethodData::Voucher(_) + | PaymentMethodData::GiftCard(_) + | PaymentMethodData::OpenBanking(_) + | PaymentMethodData::CardToken(_) + | PaymentMethodData::CardDetailsForNetworkTransactionId(_) + | PaymentMethodData::NetworkToken(_) => { + Err(errors::ConnectorError::NotImplemented( + get_unimplemented_payment_method_error_message("nexixpay"), + ))? + } + } } - PaymentMethodData::CardRedirect(_) - | PaymentMethodData::Wallet(_) - | PaymentMethodData::PayLater(_) - | PaymentMethodData::BankRedirect(_) - | PaymentMethodData::BankDebit(_) - | PaymentMethodData::BankTransfer(_) - | PaymentMethodData::Crypto(_) - | PaymentMethodData::MandatePayment - | PaymentMethodData::Reward - | PaymentMethodData::RealTimePayment(_) - | PaymentMethodData::MobilePayment(_) - | PaymentMethodData::Upi(_) - | PaymentMethodData::Voucher(_) - | PaymentMethodData::GiftCard(_) - | PaymentMethodData::OpenBanking(_) - | PaymentMethodData::CardToken(_) - | PaymentMethodData::NetworkToken(_) - | PaymentMethodData::CardDetailsForNetworkTransactionId(_) => { + Some(api_models::payments::MandateReferenceId::ConnectorMandateId(mandate_data)) => { + let contract_id = Secret::new( + mandate_data + .get_connector_mandate_request_reference_id() + .ok_or(errors::ConnectorError::MissingConnectorMandateID)?, + ); + let capture_type = + get_nexixpay_capture_type(item.router_data.request.capture_method)?; + Ok(Self::NexixpayMandatePaymentRequest(Box::new( + NexixpayMandatePaymentRequest { + contract_id, + capture_type, + }, + ))) + } + Some(api_models::payments::MandateReferenceId::NetworkTokenWithNTI(_)) + | Some(api_models::payments::MandateReferenceId::NetworkMandateId(_)) => { Err(errors::ConnectorError::NotImplemented( get_unimplemented_payment_method_error_message("nexixpay"), - ))? + ) + .into()) } } } @@ -598,45 +776,81 @@ impl PaymentsResponseData, >, ) -> Result { - let complete_authorize_url = item.data.request.get_complete_authorize_url()?; - let operation_id: String = item.response.operation.operation_id; - let redirection_form = nexixpay_threeds_link(NexixpayRedirectionRequest { - three_d_s_auth_url: item.response.three_d_s_auth_url.expose().to_string(), - three_ds_request: item.response.three_d_s_auth_request.clone(), - return_url: complete_authorize_url.clone(), - transaction_id: operation_id.clone(), - })?; - let is_auto_capture = item.data.request.is_auto_capture()?; - let connector_metadata = Some(serde_json::json!(NexixpayConnectorMetaData { - three_d_s_auth_result: None, - three_d_s_auth_response: None, - authorization_operation_id: Some(operation_id.clone()), - cancel_operation_id: None, - capture_operation_id: { - if is_auto_capture { - Some(operation_id) - } else { - None - } - }, - psync_flow: NexixpayPaymentIntent::Authorize - })); - Ok(Self { - status: AttemptStatus::from(item.response.operation.operation_result), - response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId( - item.response.operation.order_id.clone(), - ), - redirection_data: Box::new(Some(redirection_form.clone())), - mandate_reference: Box::new(None), - connector_metadata, - network_txn_id: None, - connector_response_reference_id: Some(item.response.operation.order_id), - incremental_authorization_allowed: None, - charge_id: None, + match item.response { + NexixpayPaymentsResponse::PaymentResponse(ref response_body) => { + let complete_authorize_url = item.data.request.get_complete_authorize_url()?; + let operation_id: String = response_body.operation.operation_id.clone(); + let redirection_form = nexixpay_threeds_link(NexixpayRedirectionRequest { + three_d_s_auth_url: response_body + .three_d_s_auth_url + .clone() + .expose() + .to_string(), + three_ds_request: response_body.three_d_s_auth_request.clone(), + return_url: complete_authorize_url.clone(), + transaction_id: operation_id.clone(), + })?; + let is_auto_capture = item.data.request.is_auto_capture()?; + let connector_metadata = Some(serde_json::json!(NexixpayConnectorMetaData { + three_d_s_auth_result: None, + three_d_s_auth_response: None, + authorization_operation_id: Some(operation_id.clone()), + cancel_operation_id: None, + capture_operation_id: { + if is_auto_capture { + Some(operation_id) + } else { + None + } + }, + psync_flow: NexixpayPaymentIntent::Authorize + })); + Ok(Self { + status: AttemptStatus::from(response_body.operation.operation_result.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + response_body.operation.order_id.clone(), + ), + redirection_data: Box::new(Some(redirection_form.clone())), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item + .data + .connector_mandate_request_reference_id + .clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some( + response_body.operation.order_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } + NexixpayPaymentsResponse::MandateResponse(ref mandate_response) => Ok(Self { + status: AttemptStatus::from(mandate_response.operation.operation_result.clone()), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId( + mandate_response.operation.order_id.clone(), + ), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: Some( + mandate_response.operation.order_id.clone(), + ), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data }), - ..item.data - }) + } } } @@ -738,7 +952,6 @@ impl meta_data, is_auto_capture, })?); - Ok(Self { status: AttemptStatus::from(item.response.operation.operation_result), response: Ok(PaymentsResponseData::TransactionResponse { @@ -746,7 +959,12 @@ impl item.response.operation.order_id.clone(), ), redirection_data: Box::new(None), - mandate_reference: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), connector_metadata, network_txn_id: None, connector_response_reference_id: Some(item.response.operation.order_id), @@ -775,17 +993,47 @@ impl TryFrom<&NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>> let order_id = item.router_data.connector_request_reference_id.clone(); let amount = item.amount.clone(); - let billing_address = Address { + let billing_address_street = format!( + "{}, {}", + item.router_data.get_billing_line1()?.expose(), + item.router_data.get_billing_line2()?.expose() + ); + + let billing_address = BillingAddress { name: item.router_data.get_billing_full_name()?, - street: item.router_data.get_billing_line1()?, + street: Secret::new(billing_address_street), city: item.router_data.get_billing_city()?, post_code: item.router_data.get_billing_zip()?, country: item.router_data.get_billing_country()?, }; + let shipping_address_street = match ( + item.router_data.get_optional_shipping_line1(), + item.router_data.get_optional_shipping_line2(), + ) { + (Some(line1), Some(line2)) => Some(Secret::new(format!( + "{}, {}", + line1.expose(), + line2.expose() + ))), + (Some(line1), None) => Some(Secret::new(line1.expose())), + (None, Some(line2)) => Some(Secret::new(line2.expose())), + (None, None) => None, + }; + + let shipping_address = item + .router_data + .get_optional_billing() + .map(|_| ShippingAddress { + name: item.router_data.get_optional_shipping_full_name(), + street: shipping_address_street, + city: item.router_data.get_optional_shipping_city(), + post_code: item.router_data.get_optional_shipping_zip(), + country: item.router_data.get_optional_shipping_country(), + }); let customer_info = CustomerInfo { card_holder_name: item.router_data.get_billing_full_name()?, billing_address: billing_address.clone(), - shipping_address: Some(billing_address), + shipping_address: shipping_address.clone(), }; let order_data = Order { order_id, @@ -841,12 +1089,25 @@ impl TryFrom<&NexixpayRouterData<&PaymentsCompleteAuthorizeRouterData>> .into()) } }; + let contract_id = Secret::new( + item.router_data + .connector_mandate_request_reference_id + .clone() + .ok_or_else(|| errors::ConnectorError::MissingRequiredField { + field_name: "connector_mandate_request_reference_id", + })?, + ); Ok(Self { order: order_data, card: card?, operation_id, capture_type, three_d_s_auth_data, + recurrence: Some(RecurrenceRequest { + action: NexixpayRecurringAction::ContractCreation, + contract_id, + contract_type: ContractType::MitUnscheduled, + }), }) } } @@ -870,7 +1131,12 @@ impl response: Ok(PaymentsResponseData::TransactionResponse { resource_id: ResponseId::ConnectorTransactionId(item.response.order_id.clone()), redirection_data: Box::new(None), - mandate_reference: Box::new(None), + mandate_reference: Box::new(Some(MandateReference { + connector_mandate_id: item.data.connector_mandate_request_reference_id.clone(), + payment_method_id: None, + mandate_metadata: None, + connector_mandate_request_reference_id: None, + })), connector_metadata: item.data.request.connector_meta.clone(), network_txn_id: None, connector_response_reference_id: Some(item.response.order_id.clone()), diff --git a/crates/hyperswitch_connectors/src/utils.rs b/crates/hyperswitch_connectors/src/utils.rs index 0fbc60e88dc2..c60f8e79a500 100644 --- a/crates/hyperswitch_connectors/src/utils.rs +++ b/crates/hyperswitch_connectors/src/utils.rs @@ -300,6 +300,7 @@ pub trait RouterData { fn get_optional_shipping_state(&self) -> Option>; fn get_optional_shipping_first_name(&self) -> Option>; fn get_optional_shipping_last_name(&self) -> Option>; + fn get_optional_shipping_full_name(&self) -> Option>; fn get_optional_shipping_phone_number(&self) -> Option>; fn get_optional_shipping_email(&self) -> Option; @@ -369,6 +370,12 @@ impl RouterData }) } + fn get_optional_shipping_full_name(&self) -> Option> { + self.get_optional_shipping() + .and_then(|shipping_details| shipping_details.address.as_ref()) + .and_then(|shipping_address| shipping_address.get_optional_full_name()) + } + fn get_optional_shipping_line1(&self) -> Option> { self.address.get_shipping().and_then(|shipping_address| { shipping_address @@ -1177,6 +1184,7 @@ pub trait PaymentsAuthorizeRequestData { fn get_card_holder_name_from_additional_payment_method_data( &self, ) -> Result, Error>; + fn get_connector_mandate_request_reference_id(&self) -> Result; } impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { @@ -1355,6 +1363,20 @@ impl PaymentsAuthorizeRequestData for PaymentsAuthorizeData { .into()), } } + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } } pub trait PaymentsCaptureRequestData { @@ -1514,6 +1536,7 @@ pub trait PaymentsCompleteAuthorizeRequestData { fn get_redirect_response_payload(&self) -> Result; fn get_complete_authorize_url(&self) -> Result; fn is_mandate_payment(&self) -> bool; + fn get_connector_mandate_request_reference_id(&self) -> Result; } impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { @@ -1554,6 +1577,20 @@ impl PaymentsCompleteAuthorizeRequestData for CompleteAuthorizeData { .and_then(|mandate_ids| mandate_ids.mandate_reference_id.as_ref()) .is_some() } + /// Attempts to retrieve the connector mandate reference ID as a `Result`. + fn get_connector_mandate_request_reference_id(&self) -> Result { + self.mandate_id + .as_ref() + .and_then(|mandate_ids| match &mandate_ids.mandate_reference_id { + Some(payments::MandateReferenceId::ConnectorMandateId(connector_mandate_ids)) => { + connector_mandate_ids.get_connector_mandate_request_reference_id() + } + Some(payments::MandateReferenceId::NetworkMandateId(_)) + | None + | Some(payments::MandateReferenceId::NetworkTokenWithNTI(_)) => None, + }) + .ok_or_else(missing_field_err("connector_mandate_request_reference_id")) + } } pub trait AddressData { fn get_optional_full_name(&self) -> Option>; diff --git a/crates/router/src/configs/defaults/payment_connector_required_fields.rs b/crates/router/src/configs/defaults/payment_connector_required_fields.rs index 3e6675fd48cd..c759b724414a 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -1952,6 +1952,98 @@ impl Default for settings::RequiredFields { ), } ), + ( + enums::Connector::Nexixpay, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: HashMap::new(), + common: HashMap::from( + [ + ( + "payment_method_data.card.card_number".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_number".to_string(), + display_name: "card_number".to_string(), + field_type: enums::FieldType::UserCardNumber, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_month".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_month".to_string(), + display_name: "card_exp_month".to_string(), + field_type: enums::FieldType::UserCardExpiryMonth, + value: None, + } + ), + ( + "payment_method_data.card.card_exp_year".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_exp_year".to_string(), + display_name: "card_exp_year".to_string(), + field_type: enums::FieldType::UserCardExpiryYear, + value: None, + } + ), + ( + "billing.address.line1".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line1".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine1, + value: None, + } + ), + ( + "billing.address.line2".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.line2".to_string(), + display_name: "line1".to_string(), + field_type: enums::FieldType::UserAddressLine2, + value: None, + } + ), + ( + "billing.address.city".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.city".to_string(), + display_name: "city".to_string(), + field_type: enums::FieldType::UserAddressCity, + value: None, + } + ), + ( + "billing.address.zip".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.zip".to_string(), + display_name: "zip".to_string(), + field_type: enums::FieldType::UserAddressPincode, + value: None, + } + ), + ( + "billing.address.first_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.first_name".to_string(), + display_name: "first_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ), + ( + "billing.address.last_name".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.billing.address.last_name".to_string(), + display_name: "last_name".to_string(), + field_type: enums::FieldType::UserFullName, + value: None, + } + ) + ] + ), + } + ), ( enums::Connector::Nmi, RequiredFieldFinal { diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js index 3489dbb2371a..3b77a0ad6e7f 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Nexixpay.js @@ -1,48 +1,115 @@ +const successfulNo3DSCardDetails = { + card_number: "4111111111111111", + card_exp_month: "08", + card_exp_year: "35", + card_holder_name: "joseph Doe", + card_cvc: "999", +}; + const successfulThreeDSTestCardDetails = { card_number: "4349940199004549", card_exp_month: "12", - card_exp_year: "30", + card_exp_year: "35", card_holder_name: "joseph Doe", card_cvc: "396", }; +const customerAcceptance = { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "125.0.0.1", + user_agent: "amet irure esse", + }, +}; + +const multiUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "EUR", + }, + }, +}; + +const singleUseMandateData = { + customer_acceptance: customerAcceptance, + mandate_type: { + multi_use: { + amount: 8000, + currency: "EUR", + }, + }, +}; + +const billingAddress = { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "IT", + first_name: "joseph", + last_name: "Doe", + }, + email: "mauro.morandi@nexi.it", + phone: { + number: "9123456789", + country_code: "+91", + }, +}; + +const no3DSNotSupportedResponseBody = { + error: { + type: "invalid_request", + message: "No threeds is not supported", + code: "IR_00", + }, +}; + export const connectorDetails = { card_pm: { PaymentIntent: { Request: { currency: "EUR", - amount: 3545, + amount: 6500, customer_acceptance: null, setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentOffSession: { + Request: { + currency: "EUR", + amount: 6500, + authentication_type: "no_three_ds", + customer_acceptance: null, + setup_future_usage: "off_session", }, Response: { status: 200, body: { status: "requires_payment_method", + setup_future_usage: "off_session", }, }, }, "3DSManualCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", - billing: { - address: { - line1: "1467", - line2: "CA", - line3: "CA", - city: "Florence", - state: "Tuscany", - zip: "12345", - country: "IT", - first_name: "Max", - last_name: "Mustermann", - }, - email: "mauro.morandi@nexi.it", - phone: { - number: "9123456789", - country_code: "+91", - }, - }, + billing: billingAddress, payment_method_data: { card: successfulThreeDSTestCardDetails, }, @@ -52,31 +119,17 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "requires_capture", + status: "requires_customer_action", }, }, }, "3DSAutoCapture": { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", - billing: { - address: { - line1: "1467", - line2: "CA", - line3: "CA", - city: "Florence", - state: "Tuscany", - zip: "12345", - country: "IT", - first_name: "Max", - last_name: "Mustermann", - }, - email: "mauro.morandi@nexi.it", - phone: { - number: "9123456789", - country_code: "+91", - }, - }, + billing: billingAddress, payment_method_data: { card: successfulThreeDSTestCardDetails, }, @@ -90,7 +143,42 @@ export const connectorDetails = { }, }, }, + No3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + setup_future_usage: "on_session", + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, Capture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -102,20 +190,23 @@ export const connectorDetails = { status: 200, body: { status: "processing", - amount: 3545, - amount_capturable: 0, - amount_received: 3545, + amount: 6500, + amount_capturable: 6500, + amount_received: null, }, }, }, PartialCapture: { + Configs: { + TRIGGER_SKIP: true, + }, Request: {}, Response: { status: 200, body: { status: "processing", - amount: 3545, - amount_capturable: 0, + amount: 6500, + amount_capturable: 6500, amount_received: 100, }, }, @@ -130,6 +221,9 @@ export const connectorDetails = { }, }, Refund: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -140,11 +234,14 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "pending", }, }, }, PartialRefund: { + Configs: { + TRIGGER_SKIP: true, + }, Request: { payment_method: "card", payment_method_data: { @@ -155,7 +252,7 @@ export const connectorDetails = { Response: { status: 200, body: { - status: "processing", + status: "pending", }, }, }, @@ -174,5 +271,329 @@ export const connectorDetails = { }, }, }, + MandateMultiUse3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUse3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateMultiUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateMultiUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: multiUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateSingleUse3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUse3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + MandateSingleUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + MandateSingleUseNo3DSManualCapture: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + billing: billingAddress, + }, + Response: { + status: 400, + body: no3DSNotSupportedResponseBody, + }, + }, + manualPaymentRefund: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + customer_acceptance: null, + }, + Response: { + status: 200, + body: { + status: "pending", + }, + }, + }, + ZeroAuthMandate: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + mandate_data: singleUseMandateData, + }, + Response: { + status: 200, + body: { + status: "processing", + }, + }, + }, + PaymentMethodIdMandateNo3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandateNo3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + PaymentMethodIdMandate3DSManualCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "EUR", + amount: 6500, + mandate_data: null, + authentication_type: "three_ds", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "EUR", + billing: billingAddress, + setup_future_usage: "on_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSAutoCaptureOffSession: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + currency: "EUR", + billing: billingAddress, + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + SaveCardUseNo3DSManualCaptureOffSession: { + Configs: { + TRIGGER_SKIP: true, + }, + Request: { + payment_method: "card", + currency: "EUR", + billing: billingAddress, + payment_method_type: "debit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + setup_future_usage: "off_session", + customer_acceptance: customerAcceptance, + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, }, };