diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 9c4339b7e3..c91810a990 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -6993,6 +6993,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -19361,6 +19362,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 48e376791b..587cba45e6 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9559,6 +9559,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "netcetera", "nexinets", @@ -24453,6 +24454,7 @@ "klarna", "mifinity", "mollie", + "moneris", "multisafepay", "nexinets", "nexixpay", diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 872845ceb0..79ed26906e 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -78,7 +78,7 @@ klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" -moneris.base_url = "https://api.sb.moneris.io" +moneris.base_url = "https://api.moneris.io" multisafepay.base_url = "https://testapi.multisafepay.com/" nexinets.base_url = "https://api.payengine.de/v1" nexixpay.base_url = "https://xpay.nexigroup.com/api/phoenix-0.0/psp/api/v1" diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 8a42bc274c..c52bb6816d 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -93,7 +93,7 @@ pub enum RoutableConnectors { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Nexinets, Nexixpay, @@ -233,7 +233,7 @@ pub enum Connector { Klarna, Mifinity, Mollie, - // Moneris, + Moneris, Multisafepay, Netcetera, Nexinets, @@ -318,6 +318,7 @@ impl Connector { | (Self::Deutschebank, _) | (Self::Globalpay, _) | (Self::Jpmorgan, _) + | (Self::Moneris, _) | (Self::Paypal, _) | (Self::Payu, _) | (Self::Trustpay, PaymentMethod::BankRedirect) @@ -384,7 +385,7 @@ impl Connector { | Self::Klarna | Self::Mifinity | Self::Mollie - // | Self::Moneris + | Self::Moneris | Self::Multisafepay | Self::Nexinets | Self::Nexixpay @@ -513,6 +514,7 @@ impl From for Connector { RoutableConnectors::Klarna => Self::Klarna, RoutableConnectors::Mifinity => Self::Mifinity, RoutableConnectors::Mollie => Self::Mollie, + RoutableConnectors::Moneris => Self::Moneris, RoutableConnectors::Multisafepay => Self::Multisafepay, RoutableConnectors::Nexinets => Self::Nexinets, RoutableConnectors::Nexixpay => Self::Nexixpay, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index f0cea8d149..8f1deb02c5 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -203,6 +203,7 @@ pub struct ConnectorConfig { pub klarna: Option, pub mifinity: Option, pub mollie: Option, + pub moneris: Option, pub multisafepay: Option, pub nexinets: Option, pub nexixpay: Option, @@ -367,6 +368,7 @@ impl ConnectorConfig { Connector::Klarna => Ok(connector_data.klarna), Connector::Mifinity => Ok(connector_data.mifinity), Connector::Mollie => Ok(connector_data.mollie), + Connector::Moneris => Ok(connector_data.moneris), Connector::Multisafepay => Ok(connector_data.multisafepay), Connector::Nexinets => Ok(connector_data.nexinets), Connector::Nexixpay => Ok(connector_data.nexixpay), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index c4897b2e78..266ae91877 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1878,6 +1878,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 9251edf900..e4c0e70e19 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1588,6 +1588,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index c13349325d..b7b5769047 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1826,6 +1826,48 @@ key1="Profile Token" [mollie.connector_webhook_details] merchant_secret="Source verification key" +[moneris] +[[moneris.credit]] + payment_method_type = "Mastercard" +[[moneris.credit]] + payment_method_type = "Visa" +[[moneris.credit]] + payment_method_type = "Interac" +[[moneris.credit]] + payment_method_type = "AmericanExpress" +[[moneris.credit]] + payment_method_type = "JCB" +[[moneris.credit]] + payment_method_type = "DinersClub" +[[moneris.credit]] + payment_method_type = "Discover" +[[moneris.credit]] + payment_method_type = "CartesBancaires" +[[moneris.credit]] + payment_method_type = "UnionPay" +[[moneris.debit]] + payment_method_type = "Mastercard" +[[moneris.debit]] + payment_method_type = "Visa" +[[moneris.debit]] + payment_method_type = "Interac" +[[moneris.debit]] + payment_method_type = "AmericanExpress" +[[moneris.debit]] + payment_method_type = "JCB" +[[moneris.debit]] + payment_method_type = "DinersClub" +[[moneris.debit]] + payment_method_type = "Discover" +[[moneris.debit]] + payment_method_type = "CartesBancaires" +[[moneris.debit]] + payment_method_type = "UnionPay" +[moneris.connector_auth.SignatureKey] +api_key="Client Secret" +key1="Client Id" +api_secret="Merchant Id" + [multisafepay] [[multisafepay.credit]] payment_method_type = "Mastercard" diff --git a/crates/hyperswitch_connectors/src/connectors/moneris.rs b/crates/hyperswitch_connectors/src/connectors/moneris.rs index d543a0be72..46b0ee9472 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris.rs @@ -1,10 +1,11 @@ pub mod transformers; +use common_enums::enums; use common_utils::{ errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, MinorUnit, MinorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -21,8 +22,8 @@ use hyperswitch_domain_models::{ }, router_response_types::{PaymentsResponseData, RefundsResponseData}, types::{ - PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + PaymentsSyncRouterData, RefreshTokenRouterData, RefundSyncRouterData, RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -33,23 +34,27 @@ use hyperswitch_interfaces::{ configs::Connectors, errors, events::connector_api_logs::ConnectorEvent, - types::{self, Response}, + types::{self, RefreshTokenType, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use masking::{ExposeInterface, Mask, PeekInterface}; use transformers as moneris; -use crate::{constants::headers, types::ResponseRouterData, utils}; +use crate::{ + constants::headers, + types::ResponseRouterData, + utils::{self, RefundsRequestData}, +}; #[derive(Clone)] pub struct Moneris { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Moneris { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &MinorUnitForConnector, } } } @@ -82,12 +87,31 @@ where req: &RouterData, _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), - )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + let auth = moneris::MonerisAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let mut header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + self.get_content_type().to_string().into(), + ), + ( + moneris::auth_headers::API_VERSION.to_string(), + "2024-09-17".to_string().into(), + ), + ( + moneris::auth_headers::X_MERCHANT_ID.to_string(), + auth.merchant_id.expose().into_masked(), + ), + ]; + let access_token = req + .access_token + .clone() + .ok_or(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_header = ( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.peek()).into_masked(), + ); + header.push(auth_header); Ok(header) } } @@ -117,7 +141,7 @@ impl ConnectorCommon for Moneris { .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth.client_id.expose().into_masked(), )]) } @@ -134,11 +158,20 @@ impl ConnectorCommon for Moneris { event_builder.map(|i| i.set_response_body(&response)); router_env::logger::info!(connector_response=?response); + let reason = match &response.errors { + Some(error_list) => error_list + .iter() + .map(|error| error.parameter_name.clone()) + .collect::>() + .join(" & "), + None => response.title.clone(), + }; + Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.category, + message: response.title, + reason: Some(reason), attempt_status: None, connector_transaction_id: None, }) @@ -146,16 +179,133 @@ impl ConnectorCommon for Moneris { } impl ConnectorValidation for Moneris { - //TODO: implement functions when support enabled + fn validate_connector_against_payment_request( + &self, + capture_method: Option, + _payment_method: enums::PaymentMethod, + _pmt: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple + | enums::CaptureMethod::Scheduled + | enums::CaptureMethod::SequentialAutomatic => Err( + utils::construct_not_implemented_error_report(capture_method, self.id()), + ), + } + } } impl ConnectorIntegration for Moneris { //TODO: implement sessions flow } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_url( + &self, + _req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult { + Ok(format!("{}/oauth2/token", self.base_url(connectors))) + } + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + fn get_headers( + &self, + _req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + Ok(vec![( + headers::CONTENT_TYPE.to_string(), + RefreshTokenType::get_content_type(self).to_string().into(), + )]) + } -impl ConnectorIntegration for Moneris {} + fn get_request_body( + &self, + req: &RefreshTokenRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisAuthRequest::try_from(req)?; + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefreshTokenRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + RequestBuilder::new() + .method(Method::Post) + .attach_default_headers() + .headers(RefreshTokenType::get_headers(self, req, connectors)?) + .url(&RefreshTokenType::get_url(self, req, connectors)?) + .set_body(RefreshTokenType::get_request_body(self, req, connectors)?) + .build(), + ); + Ok(req) + } + + fn handle_response( + &self, + data: &RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisAuthResponse = res + .response + .parse_struct("Moneris MonerisAuthResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + // auth error have different structure than common error + let response: moneris::MonerisAuthErrorResponse = res + .response + .parse_struct("MonerisAuthErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + Ok(ErrorResponse { + status_code: res.status_code, + code: response.error.to_string(), + message: response.error.clone(), + reason: response.error_description, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorIntegration for Moneris { + // Not Implemented (R) + fn build_request( + &self, + _req: &RouterData, + _connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err( + errors::ConnectorError::NotImplemented("Setup Mandate flow for Moneris".to_string()) + .into(), + ) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -173,9 +323,9 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/payments", self.base_url(connectors))) } fn get_request_body( @@ -259,10 +409,18 @@ impl ConnectorIntegration for Mon fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}/payments/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -323,18 +481,31 @@ impl ConnectorIntegration fo fn get_url( &self, - _req: &PaymentsCaptureRouterData, - _connectors: &Connectors, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/complete", + self.base_url(connectors) + )) } fn get_request_body( &self, - _req: &PaymentsCaptureRouterData, + req: &PaymentsCaptureRouterData, _connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount_to_capture, + req.request.currency, + )?; + + let connector_router_data = moneris::MonerisRouterData::from((amount, req)); + let connector_req = + moneris::MonerisPaymentsCaptureRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) } fn build_request( @@ -385,7 +556,77 @@ impl ConnectorIntegration fo } } -impl ConnectorIntegration for Moneris {} +impl ConnectorIntegration for Moneris { + fn get_headers( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult { + let connector_payment_id = req.request.connector_transaction_id.clone(); + Ok(format!( + "{}/payments/{connector_payment_id}/cancel", + self.base_url(connectors) + )) + } + + fn get_request_body( + &self, + req: &PaymentsCancelRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let connector_req = moneris::MonerisCancelRequest::try_from(req)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsCancelRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .set_body(types::PaymentsVoidType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &PaymentsCancelRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: moneris::MonerisPaymentsResponse = res + .response + .parse_struct("Moneris PaymentsCancelResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + RouterData::try_from(ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } +} impl ConnectorIntegration for Moneris { fn get_headers( @@ -403,9 +644,9 @@ impl ConnectorIntegration for Moneris fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}/refunds", self.base_url(connectors))) } fn get_request_body( @@ -486,10 +727,11 @@ impl ConnectorIntegration for Moneris { fn get_url( &self, - _req: &RefundSyncRouterData, - _connectors: &Connectors, + req: &RefundSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let refund_id = req.request.get_connector_refund_id()?; + Ok(format!("{}/refunds/{refund_id}", self.base_url(connectors))) } fn build_request( diff --git a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs index 0cc255d85b..b8fb3d0fc5 100644 --- a/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/moneris/transformers.rs @@ -1,31 +1,38 @@ use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_utils::types::MinorUnit; +use error_stack::ResultExt; use hyperswitch_domain_models::{ payment_method_data::PaymentMethodData, - router_data::{ConnectorAuthType, RouterData}, + router_data::{AccessToken, ConnectorAuthType, RouterData}, router_flow_types::refunds::{Execute, RSync}, router_request_types::ResponseId, router_response_types::{PaymentsResponseData, RefundsResponseData}, - types::{PaymentsAuthorizeRouterData, RefundsRouterData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCancelRouterData, PaymentsCaptureRouterData, + RefundsRouterData, + }, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; use crate::{ - types::{RefundsResponseRouterData, ResponseRouterData}, - utils::PaymentsAuthorizeRequestData, + types::{RefreshTokenRouterData, RefundsResponseRouterData, ResponseRouterData}, + utils::{ + BrowserInformationData, CardData as _, PaymentsAuthorizeRequestData, + RouterData as OtherRouterData, + }, }; -//TODO: Fill the struct with respective fields +const CLIENT_CREDENTIALS: &str = "client_credentials"; + pub struct MonerisRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: MinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for MonerisRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(MinorUnit, T)> for MonerisRouterData { + fn from((amount, item): (MinorUnit, T)) -> Self { Self { amount, router_data: item, @@ -33,20 +40,47 @@ impl From<(StringMinorUnit, T)> for MonerisRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] +pub mod auth_headers { + pub const X_MERCHANT_ID: &str = "X-Merchant-Id"; + pub const API_VERSION: &str = "Api-Version"; +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsRequest { - amount: StringMinorUnit, + idempotency_key: String, + amount: Amount, + payment_method: PaymentMethod, + automatic_capture: bool, + ipv4: Secret, +} +#[derive(Default, Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct Amount { + currency: enums::Currency, + amount: MinorUnit, +} + +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct PaymentMethod { + payment_method_source: PaymentMethodSource, card: MonerisCard, } +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum PaymentMethodSource { + Card, +} + #[derive(Default, Debug, Serialize, Eq, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, + card_number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + card_security_code: Secret, } impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymentsRequest { @@ -54,18 +88,51 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen fn try_from( item: &MonerisRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + if item.router_data.is_three_ds() { + Err(errors::ConnectorError::NotSupported { + message: "Card 3DS".to_string(), + connector: "Moneris", + })? + }; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(req_card) => { - let card = MonerisCard { - number: req_card.card_number, - expiry_month: req_card.card_exp_month, - expiry_year: req_card.card_exp_year, - cvc: req_card.card_cvc, - complete: item.router_data.request.is_auto_capture()?, + PaymentMethodData::Card(ref req_card) => { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, }; + let payment_method = PaymentMethod { + payment_method_source: PaymentMethodSource::Card, + card: MonerisCard { + card_number: req_card.card_number.clone(), + expiry_month: Secret::new( + req_card + .card_exp_month + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + expiry_year: Secret::new( + req_card + .get_expiry_year_4_digit() + .peek() + .parse::() + .change_context(errors::ConnectorError::ParsingFailed)?, + ), + card_security_code: req_card.card_cvc.clone(), + }, + }; + let automatic_capture = item.router_data.request.is_auto_capture()?; + + let browser_info = item.router_data.request.get_browser_info()?; + let ipv4 = browser_info.get_ip_address()?; + Ok(Self { - amount: item.amount.clone(), - card, + idempotency_key, + amount, + payment_method, + automatic_capture, + ipv4, }) } _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), @@ -73,49 +140,106 @@ impl TryFrom<&MonerisRouterData<&PaymentsAuthorizeRouterData>> for MonerisPaymen } } -//TODO: Fill the struct with respective fields -// Auth Struct pub struct MonerisAuthType { - pub(super) api_key: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, + pub(super) merchant_id: Secret, } impl TryFrom<&ConnectorAuthType> for MonerisAuthType { type Error = error_stack::Report; fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + client_id: key1.to_owned(), + client_secret: api_key.to_owned(), + merchant_id: api_secret.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags + +#[derive(Debug, Clone, Serialize, PartialEq)] +pub struct MonerisAuthRequest { + client_id: Secret, + client_secret: Secret, + grant_type: String, +} + +impl TryFrom<&RefreshTokenRouterData> for MonerisAuthRequest { + type Error = error_stack::Report; + fn try_from(item: &RefreshTokenRouterData) -> Result { + let auth = MonerisAuthType::try_from(&item.connector_auth_type)?; + Ok(Self { + client_id: auth.client_id.clone(), + client_secret: auth.client_secret.clone(), + grant_type: CLIENT_CREDENTIALS.to_string(), + }) + } +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct MonerisAuthResponse { + access_token: Secret, + token_type: String, + expires_in: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(AccessToken { + token: item.response.access_token, + expires: item + .response + .expires_in + .parse::() + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?, + }), + ..item.data + }) + } +} + #[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum MonerisPaymentStatus { Succeeded, - Failed, #[default] Processing, + Canceled, + Declined, + DeclinedRetry, + Authorized, } impl From for common_enums::AttemptStatus { fn from(item: MonerisPaymentStatus) -> Self { match item { MonerisPaymentStatus::Succeeded => Self::Charged, - MonerisPaymentStatus::Failed => Self::Failure, - MonerisPaymentStatus::Processing => Self::Authorizing, + MonerisPaymentStatus::Authorized => Self::Authorized, + MonerisPaymentStatus::Canceled => Self::Voided, + MonerisPaymentStatus::Declined | MonerisPaymentStatus::DeclinedRetry => Self::Failure, + MonerisPaymentStatus::Processing => Self::Pending, } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisPaymentsResponse { - status: MonerisPaymentStatus, - id: String, + payment_status: MonerisPaymentStatus, + payment_id: String, } impl TryFrom> @@ -126,14 +250,14 @@ impl TryFrom, ) -> Result { Ok(Self { - status: common_enums::AttemptStatus::from(item.response.status), + status: common_enums::AttemptStatus::from(item.response.payment_status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::ConnectorTransactionId(item.response.payment_id.clone()), redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.payment_id), incremental_authorization_allowed: None, charges: None, }), @@ -142,50 +266,101 @@ impl TryFrom> for MonerisPaymentsCaptureRequest { + type Error = error_stack::Report; + fn try_from(item: &MonerisRouterData<&PaymentsCaptureRouterData>) -> Result { + let amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + Ok(Self { + amount, + idempotency_key, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct MonerisCancelRequest { + idempotency_key: String, + reason: Option, +} + +impl TryFrom<&PaymentsCancelRouterData> for MonerisCancelRequest { + type Error = error_stack::Report; + fn try_from(item: &PaymentsCancelRouterData) -> Result { + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.request.cancellation_reason.clone(); + Ok(Self { + idempotency_key, + reason, + }) + } +} + #[derive(Default, Debug, Serialize)] +#[serde(rename_all = "camelCase")] pub struct MonerisRefundRequest { - pub amount: StringMinorUnit, + pub refund_amount: Amount, + pub idempotency_key: String, + pub reason: Option, + pub payment_id: String, } impl TryFrom<&MonerisRouterData<&RefundsRouterData>> for MonerisRefundRequest { type Error = error_stack::Report; fn try_from(item: &MonerisRouterData<&RefundsRouterData>) -> Result { + let refund_amount = Amount { + currency: item.router_data.request.currency, + amount: item.amount, + }; + let idempotency_key = uuid::Uuid::new_v4().to_string(); + let reason = item.router_data.request.reason.clone(); + let payment_id = item.router_data.request.connector_transaction_id.clone(); Ok(Self { - amount: item.amount.to_owned(), + refund_amount, + idempotency_key, + reason, + payment_id, }) } } -// Type definition for Refund Response - #[allow(dead_code)] #[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum RefundStatus { Succeeded, - Failed, #[default] Processing, + Declined, + DeclinedRetry, } impl From for enums::RefundStatus { fn from(item: RefundStatus) -> Self { match item { RefundStatus::Succeeded => Self::Success, - RefundStatus::Failed => Self::Failure, + RefundStatus::Declined | RefundStatus::DeclinedRetry => Self::Failure, RefundStatus::Processing => Self::Pending, - //TODO: Review mapping } } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + refund_id: String, + refund_status: RefundStatus, } impl TryFrom> for RefundsRouterData { @@ -195,8 +370,8 @@ impl TryFrom> for RefundsRout ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) @@ -210,19 +385,32 @@ impl TryFrom> for RefundsRouter ) -> Result { Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.refund_id.to_string(), + refund_status: enums::RefundStatus::from(item.response.refund_status), }), ..item.data }) } } -//TODO: Fill the struct with respective fields #[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] pub struct MonerisErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub status: u16, + pub category: String, + pub title: String, + pub errors: Option>, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct MonerisError { + pub reason_code: String, + pub parameter_name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub struct MonerisAuthErrorResponse { + pub error: String, + pub error_description: 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 a9f5ae5949..27cc485188 100644 --- a/crates/router/src/configs/defaults/payment_connector_required_fields.rs +++ b/crates/router/src/configs/defaults/payment_connector_required_fields.rs @@ -1997,6 +1997,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: 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, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { @@ -5457,6 +5504,53 @@ impl Default for settings::RequiredFields { common: HashMap::new(), } ), + ( + enums::Connector::Moneris, + RequiredFieldFinal { + mandate: HashMap::new(), + non_mandate: 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, + } + ), + ( + "payment_method_data.card.card_cvc".to_string(), + RequiredFieldInfo { + required_field: "payment_method_data.card.card_cvc".to_string(), + display_name: "card_cvc".to_string(), + field_type: enums::FieldType::UserCardCvc, + value: None, + } + ), + ] + ), + common: HashMap::new(), + } + ), ( enums::Connector::Multisafepay, RequiredFieldFinal { diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 601fbd1623..98b55cd982 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1433,6 +1433,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { mollie::transformers::MollieAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Moneris => { + moneris::transformers::MonerisAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Multisafepay => { multisafepay::transformers::MultisafepayAuthType::try_from(self.auth_type)?; Ok(()) diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 1a702cb835..43556eb581 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -464,6 +464,9 @@ impl ConnectorData { // enums::Connector::Moneris => Ok(ConnectorEnum::Old(Box::new(connector::Moneris))), Ok(ConnectorEnum::Old(Box::new(connector::Mollie::new()))) } + enums::Connector::Moneris => { + Ok(ConnectorEnum::Old(Box::new(connector::Moneris::new()))) + } enums::Connector::Nexixpay => { Ok(ConnectorEnum::Old(Box::new(connector::Nexixpay::new()))) } diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index 7ac8792b50..aa27e362df 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -263,7 +263,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, - // api_enums::Connector::Moneris => Self::Moneris, + api_enums::Connector::Moneris => Self::Moneris, api_enums::Connector::Multisafepay => Self::Multisafepay, api_enums::Connector::Netcetera => { Err(common_utils::errors::ValidationError::InvalidValue { diff --git a/crates/router/tests/connectors/moneris.rs b/crates/router/tests/connectors/moneris.rs index ac98cc17f3..ba8c7e491f 100644 --- a/crates/router/tests/connectors/moneris.rs +++ b/crates/router/tests/connectors/moneris.rs @@ -13,7 +13,7 @@ impl utils::Connector for MonerisTest { use router::connector::Moneris; utils::construct_connector_data_old( Box::new(Moneris::new()), - types::Connector::Plaid, + types::Connector::Moneris, api::GetToken::Connector, None, ) diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 30de321a4b..7a5b48f03a 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -57,7 +57,7 @@ pub struct ConnectorAuthentication { pub jpmorgan: Option, pub mifinity: Option, pub mollie: Option, - pub moneris: Option, + pub moneris: Option, pub multisafepay: Option, pub netcetera: Option, pub nexinets: Option, diff --git a/cypress-tests/cypress/e2e/configs/Payment/Moneris.js b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js new file mode 100644 index 0000000000..7382c6a25d --- /dev/null +++ b/cypress-tests/cypress/e2e/configs/Payment/Moneris.js @@ -0,0 +1,316 @@ +import { getCustomExchange } from "./Modifiers"; + +const successfulNo3DSCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "John", + card_cvc: "123", +}; + +const successfulThreeDSTestCardDetails = { + card_number: "4242424242424242", + card_exp_month: "01", + card_exp_year: "27", + card_holder_name: "Joseph", + card_cvc: "123", +}; + +export const connectorDetails = { + card_pm: { + PaymentIntent: { + Request: { + currency: "USD", + amount: 5000, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + PaymentIntentWithShippingCost: { + Request: { + currency: "USD", + amount: 5000, + shipping_cost: 50, + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + amount: 5000, + shipping_cost: 50, + }, + }, + }, + PaymentConfirmWithShippingCost: { + Request: { + payment_method: "card", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + shipping_cost: 50, + amount_received: 5050, + amount: 5000, + net_amount: 5050, + }, + }, + }, + "3DSManualCapture": getCustomExchange({ + Request: { + amount: 5000, + payment_method: "card", + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + "3DSAutoCapture": getCustomExchange({ + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulThreeDSTestCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + }), + No3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + No3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + customer_acceptance: null, + setup_future_usage: "on_session", + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + Capture: { + Request: { + amount_to_capture: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + amount: 5000, + amount_capturable: 0, + amount_received: 5000, + }, + }, + }, + PartialCapture: { + Request: { + amount_to_capture: 2000, + }, + Response: { + status: 200, + body: { + status: "partially_captured", + amount: 5000, + amount_capturable: 0, + amount_received: 2000, + }, + }, + }, + Void: { + Request: {}, + Response: { + status: 200, + body: { + status: "cancelled", + }, + }, + }, + Refund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + PartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentRefund: { + Request: { + amount: 5000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + manualPaymentPartialRefund: { + Request: { + amount: 2000, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SyncRefund: { + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + ZeroAuthMandate: { + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + ZeroAuthPaymentIntent: { + Request: { + amount: 0, + setup_future_usage: "off_session", + currency: "USD", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + setup_future_usage: "off_session", + }, + }, + }, + ZeroAuthConfirmPayment: { + Request: { + payment_type: "setup_mandate", + payment_method: "card", + payment_method_type: "credit", + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + }, + Response: { + status: 501, + body: { + error: { + type: "invalid_request", + message: "Setup Mandate flow for Moneris is not implemented", + code: "IR_00", + }, + }, + }, + }, + SaveCardUseNo3DSAutoCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "succeeded", + }, + }, + }, + SaveCardUseNo3DSManualCapture: { + Request: { + payment_method: "card", + amount: 5000, + payment_method_data: { + card: successfulNo3DSCardDetails, + }, + currency: "USD", + setup_future_usage: "on_session", + customer_acceptance: { + acceptance_type: "offline", + accepted_at: "1963-05-03T04:07:52.723Z", + online: { + ip_address: "127.0.0.1", + user_agent: "amet irure esse", + }, + }, + }, + Response: { + status: 200, + body: { + status: "requires_capture", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/configs/Payment/Utils.js b/cypress-tests/cypress/e2e/configs/Payment/Utils.js index b3de4db78d..8a7b559b95 100644 --- a/cypress-tests/cypress/e2e/configs/Payment/Utils.js +++ b/cypress-tests/cypress/e2e/configs/Payment/Utils.js @@ -14,6 +14,7 @@ import { connectorDetails as fiuuConnectorDetails } from "./Fiuu.js"; import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; import { connectorDetails as jpmorganConnectorDetails } from "./Jpmorgan.js"; +import { connectorDetails as monerisConnectorDetails } from "./Moneris.js"; import { connectorDetails as nexixpayConnectorDetails } from "./Nexixpay.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as noonConnectorDetails } from "./Noon.js"; @@ -39,6 +40,7 @@ const connectorDetails = { iatapay: iatapayConnectorDetails, itaubank: itaubankConnectorDetails, jpmorgan: jpmorganConnectorDetails, + moneris: monerisConnectorDetails, nexixpay: nexixpayConnectorDetails, nmi: nmiConnectorDetails, novalnet: novalnetConnectorDetails,