diff --git a/config/config.example.toml b/config/config.example.toml index f0dad483c6..d43668c446 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -194,6 +194,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" @@ -259,7 +260,8 @@ stripe = { banks = "alior_bank,bank_millennium,bank_nowy_bfg_sa,bank_pekao_sa,ba # This data is used to call respective connectors for wallets and cards [connectors.supported] -wallets = ["klarna", "braintree", "applepay"] +wallets = ["klarna", + "mifinity", "braintree", "applepay"] rewards = ["cashtocode", "zen"] cards = [ "adyen", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index faf3bb7f46..1c4dc0e7bf 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -49,6 +49,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index f564b976bc..e136c05342 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -53,6 +53,7 @@ gocardless.base_url = "https://api.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.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/" multisafepay.base_url = "https://testapi.multisafepay.com/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 35c8a2ba3d..3468c443a1 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -53,6 +53,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" diff --git a/config/development.toml b/config/development.toml index 3f05872bc6..83730662ad 100644 --- a/config/development.toml +++ b/config/development.toml @@ -88,7 +88,8 @@ vault_private_key = "" tunnel_private_key = "" [connectors.supported] -wallets = ["klarna", "braintree", "applepay", "adyen"] +wallets = ["klarna", + "mifinity", "braintree", "applepay", "adyen"] rewards = ["cashtocode", "zen"] cards = [ "aci", @@ -193,6 +194,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 9fd22f4670..7dfab5548b 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -128,6 +128,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" @@ -172,7 +173,8 @@ zsl.base_url = "https://api.sitoffalb.net/" apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } [connectors.supported] -wallets = ["klarna", "braintree", "applepay"] +wallets = ["klarna", + "mifinity", "braintree", "applepay"] rewards = ["cashtocode", "zen"] cards = [ "aci", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index aed9915366..93e20dc2e2 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -98,6 +98,7 @@ pub enum Connector { Helcim, Iatapay, Klarna, + // Mifinity, Added as template code for future usage Mollie, Multisafepay, Netcetera, @@ -210,6 +211,7 @@ impl Connector { | Self::Helcim | Self::Iatapay | Self::Klarna + // | Self::Mifinity Added as template code for future usage | Self::Mollie | Self::Multisafepay | Self::Nexinets diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 3c86064731..70dc1ade0a 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -137,6 +137,7 @@ pub enum RoutableConnectors { Helcim, Iatapay, Klarna, + Mifinity, Mollie, Multisafepay, Nexinets, diff --git a/crates/router/src/configs/settings.rs b/crates/router/src/configs/settings.rs index 5b55ca8b0e..a07fa9ad2b 100644 --- a/crates/router/src/configs/settings.rs +++ b/crates/router/src/configs/settings.rs @@ -515,6 +515,7 @@ pub struct Connectors { pub helcim: ConnectorParams, pub iatapay: ConnectorParams, pub klarna: ConnectorParams, + pub mifinity: ConnectorParams, pub mollie: ConnectorParams, pub multisafepay: ConnectorParams, pub netcetera: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index 01c22651cd..3f61a554f8 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -26,6 +26,7 @@ pub mod gocardless; pub mod helcim; pub mod iatapay; pub mod klarna; +pub mod mifinity; pub mod mollie; pub mod multisafepay; pub mod netcetera; @@ -69,11 +70,11 @@ pub use self::{ checkout::Checkout, coinbase::Coinbase, cryptopay::Cryptopay, cybersource::Cybersource, dlocal::Dlocal, ebanx::Ebanx, fiserv::Fiserv, forte::Forte, globalpay::Globalpay, globepay::Globepay, gocardless::Gocardless, helcim::Helcim, iatapay::Iatapay, klarna::Klarna, - mollie::Mollie, multisafepay::Multisafepay, netcetera::Netcetera, nexinets::Nexinets, nmi::Nmi, - noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, payeezy::Payeezy, payme::Payme, - paypal::Paypal, payu::Payu, placetopay::Placetopay, powertranz::Powertranz, - prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, shift4::Shift4, signifyd::Signifyd, - square::Square, stax::Stax, stripe::Stripe, threedsecureio::Threedsecureio, trustpay::Trustpay, - tsys::Tsys, volt::Volt, wise::Wise, worldline::Worldline, worldpay::Worldpay, zen::Zen, - zsl::Zsl, + mifinity::Mifinity, mollie::Mollie, multisafepay::Multisafepay, netcetera::Netcetera, + nexinets::Nexinets, nmi::Nmi, noon::Noon, nuvei::Nuvei, opayo::Opayo, opennode::Opennode, + payeezy::Payeezy, payme::Payme, paypal::Paypal, payu::Payu, placetopay::Placetopay, + powertranz::Powertranz, prophetpay::Prophetpay, rapyd::Rapyd, riskified::Riskified, + shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripe::Stripe, + threedsecureio::Threedsecureio, trustpay::Trustpay, tsys::Tsys, volt::Volt, wise::Wise, + worldline::Worldline, worldpay::Worldpay, zen::Zen, zsl::Zsl, }; diff --git a/crates/router/src/connector/mifinity.rs b/crates/router/src/connector/mifinity.rs new file mode 100644 index 0000000000..7183dd755e --- /dev/null +++ b/crates/router/src/connector/mifinity.rs @@ -0,0 +1,561 @@ +pub mod transformers; + +use std::fmt::Debug; + +use error_stack::{report, ResultExt}; +use masking::ExposeInterface; +use transformers as mifinity; + +use crate::{ + configs::settings, + core::errors::{self, CustomResult}, + events::connector_api_logs::ConnectorEvent, + headers, + services::{ + self, + request::{self, Mask}, + ConnectorIntegration, ConnectorValidation, + }, + types::{ + self, + api::{self, ConnectorCommon, ConnectorCommonExt}, + ErrorResponse, RequestContent, Response, + }, + utils::BytesExt, +}; + +#[derive(Debug, Clone)] +pub struct Mifinity; + +impl api::Payment for Mifinity {} +impl api::PaymentSession for Mifinity {} +impl api::ConnectorAccessToken for Mifinity {} +impl api::MandateSetup for Mifinity {} +impl api::PaymentAuthorize for Mifinity {} +impl api::PaymentSync for Mifinity {} +impl api::PaymentCapture for Mifinity {} +impl api::PaymentVoid for Mifinity {} +impl api::Refund for Mifinity {} +impl api::RefundExecute for Mifinity {} +impl api::RefundSync for Mifinity {} +impl api::PaymentToken for Mifinity {} + +impl + ConnectorIntegration< + api::PaymentMethodToken, + types::PaymentMethodTokenizationData, + types::PaymentsResponseData, + > for Mifinity +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Mifinity +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + req: &types::RouterData, + _connectors: &settings::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); + Ok(header) + } +} + +impl ConnectorCommon for Mifinity { + fn id(&self) -> &'static str { + "mifinity" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a settings::Connectors) -> &'a str { + connectors.mifinity.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &types::ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = mifinity::MifinityAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.api_key.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: mifinity::MifinityErrorResponse = res + .response + .parse_struct("MifinityErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.code, + message: response.message, + reason: response.reason, + attempt_status: None, + connector_transaction_id: None, + }) + } +} + +impl ConnectorValidation for Mifinity { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration + for Mifinity +{ + //TODO: implement sessions flow +} + +impl ConnectorIntegration + for Mifinity +{ +} + +impl + ConnectorIntegration< + api::SetupMandate, + types::SetupMandateRequestData, + types::PaymentsResponseData, + > for Mifinity +{ +} + +impl ConnectorIntegration + for Mifinity +{ + fn get_headers( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::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: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::PaymentsAuthorizeRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = mifinity::MifinityRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let connector_req = mifinity::MifinityPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::PaymentsAuthorizeRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsAuthorizeType::get_url( + self, req, connectors, + )?) + .attach_default_headers() + .headers(types::PaymentsAuthorizeType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsAuthorizeType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: mifinity::MifinityPaymentsResponse = res + .response + .parse_struct("Mifinity PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Mifinity +{ + fn get_headers( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::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: &types::PaymentsSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::PaymentsSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsSyncType::get_headers(self, req, connectors)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: mifinity::MifinityPaymentsResponse = res + .response + .parse_struct("mifinity PaymentsSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Mifinity +{ + fn get_headers( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::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: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &types::PaymentsCaptureRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::PaymentsCaptureRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsCaptureType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsCaptureType::get_headers( + self, req, connectors, + )?) + .set_body(types::PaymentsCaptureType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: mifinity::MifinityPaymentsResponse = res + .response + .parse_struct("Mifinity PaymentsCaptureResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration + for Mifinity +{ +} + +impl ConnectorIntegration + for Mifinity +{ + fn get_headers( + &self, + req: &types::RefundsRouterData, + connectors: &settings::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: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &types::RefundsRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_router_data = mifinity::MifinityRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let connector_req = mifinity::MifinityRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::RefundsRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::RefundExecuteType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundExecuteType::get_headers( + self, req, connectors, + )?) + .set_body(types::RefundExecuteType::get_request_body( + self, req, connectors, + )?) + .build(); + Ok(Some(request)) + } + + fn handle_response( + &self, + data: &types::RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: mifinity::RefundResponse = res + .response + .parse_struct("mifinity RefundResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Mifinity { + fn get_headers( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::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: &types::RefundSyncRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &types::RefundSyncRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Get) + .url(&types::RefundSyncType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::RefundSyncType::get_headers(self, req, connectors)?) + .set_body(types::RefundSyncType::get_request_body( + self, req, connectors, + )?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: mifinity::RefundResponse = res + .response + .parse_struct("mifinity RefundSyncResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl api::IncomingWebhook for Mifinity { + fn get_webhook_object_reference_id( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &api::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} diff --git a/crates/router/src/connector/mifinity/transformers.rs b/crates/router/src/connector/mifinity/transformers.rs new file mode 100644 index 0000000000..e1c3d18d0b --- /dev/null +++ b/crates/router/src/connector/mifinity/transformers.rs @@ -0,0 +1,244 @@ +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + connector::utils::PaymentsAuthorizeRequestData, + core::errors, + types::{self, api, domain, storage::enums}, +}; + +//TODO: Fill the struct with respective fields +pub struct MifinityRouterData { + pub amount: i64, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for MifinityRouterData +{ + type Error = error_stack::Report; + fn try_from( + (_currency_unit, _currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Ok(Self { + amount, + router_data: item, + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct MifinityPaymentsRequest { + amount: i64, + card: MifinityCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct MifinityCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&MifinityRouterData<&types::PaymentsAuthorizeRouterData>> for MifinityPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &MifinityRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + domain::PaymentMethodData::Card(req_card) => { + let card = MifinityCard { + 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()?, + }; + Ok(Self { + amount: item.amount.to_owned(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct MifinityAuthType { + pub(super) api_key: Secret, +} + +impl TryFrom<&types::ConnectorAuthType> for MifinityAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &types::ConnectorAuthType) -> Result { + match auth_type { + types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { + api_key: api_key.to_owned(), + }), + _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), + } + } +} +// PaymentsResponse +//TODO: Append the remaining status flags +#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "lowercase")] +pub enum MifinityPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::AttemptStatus { + fn from(item: MifinityPaymentStatus) -> Self { + match item { + MifinityPaymentStatus::Succeeded => Self::Charged, + MifinityPaymentStatus::Failed => Self::Failure, + MifinityPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct MifinityPaymentsResponse { + status: MifinityPaymentStatus, + id: String, +} + +impl + TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + MifinityPaymentsResponse, + T, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct MifinityRefundRequest { + pub amount: i64, +} + +impl TryFrom<&MifinityRouterData<&types::RefundsRouterData>> for MifinityRefundRequest { + type Error = error_stack::Report; + fn try_from( + item: &MifinityRouterData<&types::RefundsRouterData>, + ) -> Result { + Ok(Self { + amount: item.amount.to_owned(), + }) + } +} + +// Type definition for Refund Response + +#[allow(dead_code)] +#[derive(Debug, Serialize, Default, Deserialize, Clone)] +pub enum RefundStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for enums::RefundStatus { + fn from(item: RefundStatus) -> Self { + match item { + RefundStatus::Succeeded => Self::Success, + RefundStatus::Failed => Self::Failure, + RefundStatus::Processing => Self::Pending, + //TODO: Review mapping + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize)] +pub struct RefundResponse { + id: String, + status: RefundStatus, +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> + for types::RefundsRouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct MifinityErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index ee06097a59..e0cfe0dee8 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1788,6 +1788,10 @@ pub(crate) fn validate_auth_and_metadata_type( use crate::connector::*; match connector_name { + // api_enums::Connector::Mifinity => { + // mifinity::transformers::MifinityAuthType::try_from(val)?; + // Ok(()) + // } Added as template code for future usage #[cfg(feature = "dummy_connector")] api_enums::Connector::DummyConnector1 | api_enums::Connector::DummyConnector2 diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index 0687f5986b..6980a362f7 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -160,6 +160,7 @@ default_imp_for_complete_authorize!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Multisafepay, connector::Netcetera, connector::Nexinets, @@ -238,6 +239,7 @@ default_imp_for_webhook_source_verification!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -324,6 +326,7 @@ default_imp_for_create_customer!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -405,6 +408,7 @@ default_imp_for_connector_redirect_response!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Multisafepay, connector::Netcetera, connector::Nexinets, @@ -468,6 +472,7 @@ default_imp_for_connector_request_id!( connector::Gocardless, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -556,6 +561,7 @@ default_imp_for_accept_dispute!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -665,6 +671,7 @@ default_imp_for_file_upload!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -751,6 +758,7 @@ default_imp_for_submit_evidence!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -837,6 +845,7 @@ default_imp_for_defend_dispute!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -921,6 +930,7 @@ default_imp_for_pre_processing_steps!( connector::Globepay, connector::Helcim, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -985,6 +995,7 @@ default_imp_for_payouts!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1069,6 +1080,7 @@ default_imp_for_payouts_create!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1156,6 +1168,7 @@ default_imp_for_payouts_eligibility!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1241,6 +1254,7 @@ default_imp_for_payouts_fulfill!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1325,6 +1339,7 @@ default_imp_for_payouts_cancel!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1411,6 +1426,7 @@ default_imp_for_payouts_quote!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1498,6 +1514,7 @@ default_imp_for_payouts_recipient!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1588,6 +1605,7 @@ default_imp_for_payouts_recipient_account!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1675,6 +1693,7 @@ default_imp_for_approve!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1763,6 +1782,7 @@ default_imp_for_reject!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1835,6 +1855,7 @@ default_imp_for_fraud_check!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -1923,6 +1944,7 @@ default_imp_for_frm_sale!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2011,6 +2033,7 @@ default_imp_for_frm_checkout!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2099,6 +2122,7 @@ default_imp_for_frm_transaction!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2187,6 +2211,7 @@ default_imp_for_frm_fulfillment!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2275,6 +2300,7 @@ default_imp_for_frm_record_return!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2360,6 +2386,7 @@ default_imp_for_incremental_authorization!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2445,6 +2472,7 @@ default_imp_for_revoking_mandates!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Netcetera, @@ -2572,6 +2600,7 @@ default_imp_for_connector_authentication!( connector::Helcim, connector::Iatapay, connector::Klarna, + connector::Mifinity, connector::Mollie, connector::Multisafepay, connector::Nexinets, diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 57f51c7b8b..4eb4538e77 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -361,6 +361,7 @@ impl ConnectorData { enums::Connector::Helcim => Ok(Box::new(&connector::Helcim)), enums::Connector::Iatapay => Ok(Box::new(&connector::Iatapay)), enums::Connector::Klarna => Ok(Box::new(&connector::Klarna)), + // enums::Connector::Mifinity => Ok(Box::new(&connector::Mifinity)), Added as template code for future usage enums::Connector::Mollie => Ok(Box::new(&connector::Mollie)), enums::Connector::Nmi => Ok(Box::new(&connector::Nmi)), enums::Connector::Noon => Ok(Box::new(&connector::Noon)), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index ce11251b83..8b8ba753db 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -231,6 +231,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, api_enums::Connector::Klarna => Self::Klarna, + // api_enums::Connector::Mifinity => Self::Mifinity, Added as template code for future usage api_enums::Connector::Mollie => Self::Mollie, api_enums::Connector::Multisafepay => Self::Multisafepay, api_enums::Connector::Netcetera => { diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index 78e67b7670..2682c1fcf9 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -34,6 +34,7 @@ mod globepay; mod gocardless; mod helcim; mod iatapay; +mod mifinity; mod mollie; mod multisafepay; mod netcetera; diff --git a/crates/router/tests/connectors/mifinity.rs b/crates/router/tests/connectors/mifinity.rs new file mode 100644 index 0000000000..f0b5834268 --- /dev/null +++ b/crates/router/tests/connectors/mifinity.rs @@ -0,0 +1,421 @@ +use masking::Secret; +use router::types::{self, domain, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct MifinityTest; +impl ConnectorActions for MifinityTest {} +impl utils::Connector for MifinityTest { + fn get_data(&self) -> types::api::ConnectorData { + use router::connector::Mifinity; + types::api::ConnectorData { + connector: Box::new(&Mifinity), + connector_name: types::Connector::Adyen, + // Added as Dummy connector as template code is added for future usage + get_token: types::api::GetToken::Connector, + merchant_connector_id: None, + } + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .mifinity + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "mifinity".to_string() + } +} + +static CONNECTOR: MifinityTest = MifinityTest {}; + +fn get_default_payment_info() -> Option { + None +} + +fn payment_method_details() -> Option { + None +} + +// Cards Positive Tests +// Creates a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_only_authorize_payment() { + let response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized); +} + +// Captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment(payment_method_details(), None, get_default_payment_info()) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Partially captures a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_capture_authorized_payment() { + let response = CONNECTOR + .authorize_and_capture_payment( + payment_method_details(), + Some(types::PaymentsCaptureData { + amount_to_capture: 50, + ..utils::PaymentCaptureType::default().0 + }), + get_default_payment_info(), + ) + .await + .expect("Capture payment response"); + assert_eq!(response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_authorized_payment() { + let authorize_response = CONNECTOR + .authorize_payment(payment_method_details(), get_default_payment_info()) + .await + .expect("Authorize payment response"); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Authorized, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("PSync response"); + assert_eq!(response.status, enums::AttemptStatus::Authorized,); +} + +// Voids a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_void_authorized_payment() { + let response = CONNECTOR + .authorize_and_void_payment( + payment_method_details(), + Some(types::PaymentsCancelData { + connector_transaction_id: String::from(""), + cancellation_reason: Some("requested_by_customer".to_string()), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .expect("Void payment response"); + assert_eq!(response.status, enums::AttemptStatus::Voided); +} + +// Refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_manually_captured_payment() { + let response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Synchronizes a refund using the manual capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_manually_captured_refund() { + let refund_response = CONNECTOR + .capture_payment_and_refund( + payment_method_details(), + None, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_make_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); +} + +// Synchronizes a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_auto_captured_payment() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let response = CONNECTOR + .psync_retry_till_status_matches( + enums::AttemptStatus::Charged, + Some(types::PaymentsSyncData { + connector_transaction_id: router::types::ResponseId::ConnectorTransactionId( + txn_id.unwrap(), + ), + capture_method: Some(enums::CaptureMethod::Automatic), + ..Default::default() + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!(response.status, enums::AttemptStatus::Charged,); +} + +// Refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_auto_captured_payment() { + let response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Partially refunds a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_partially_refund_succeeded_payment() { + let refund_response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + refund_response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Creates multiple refunds against a payment using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_refund_succeeded_payment_multiple_times() { + CONNECTOR + .make_payment_and_multiple_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 50, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await; +} + +// Synchronizes a refund using the automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_sync_refund() { + let refund_response = CONNECTOR + .make_payment_and_refund(payment_method_details(), None, get_default_payment_info()) + .await + .unwrap(); + let response = CONNECTOR + .rsync_retry_till_status_matches( + enums::RefundStatus::Success, + refund_response.response.unwrap().connector_refund_id, + None, + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap().refund_status, + enums::RefundStatus::Success, + ); +} + +// Cards Negative scenarios +// Creates a payment with incorrect CVC. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_cvc() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + card_cvc: Secret::new("12345".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's security code is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry month. +#[actix_web::test] +async fn should_fail_payment_for_invalid_exp_month() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + card_exp_month: Secret::new("20".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration month is invalid.".to_string(), + ); +} + +// Creates a payment with incorrect expiry year. +#[actix_web::test] +async fn should_fail_payment_for_incorrect_expiry_year() { + let response = CONNECTOR + .make_payment( + Some(types::PaymentsAuthorizeData { + payment_method_data: types::domain::PaymentMethodData::Card(domain::Card { + card_exp_year: Secret::new("2000".to_string()), + ..utils::CCardType::default().0 + }), + ..utils::PaymentAuthorizeType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Your card's expiration year is invalid.".to_string(), + ); +} + +// Voids a payment using automatic capture flow (Non 3DS). +#[actix_web::test] +async fn should_fail_void_payment_for_auto_capture() { + let authorize_response = CONNECTOR + .make_payment(payment_method_details(), get_default_payment_info()) + .await + .unwrap(); + assert_eq!(authorize_response.status, enums::AttemptStatus::Charged); + let txn_id = utils::get_connector_transaction_id(authorize_response.response); + assert_ne!(txn_id, None, "Empty connector transaction id"); + let void_response = CONNECTOR + .void_payment(txn_id.unwrap(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + void_response.response.unwrap_err().message, + "You cannot cancel this PaymentIntent because it has a status of succeeded." + ); +} + +// Captures a payment using invalid connector payment id. +#[actix_web::test] +async fn should_fail_capture_for_invalid_payment() { + let capture_response = CONNECTOR + .capture_payment("123456789".to_string(), None, get_default_payment_info()) + .await + .unwrap(); + assert_eq!( + capture_response.response.unwrap_err().message, + String::from("No such payment_intent: '123456789'") + ); +} + +// Refunds a payment with refund amount higher than payment amount. +#[actix_web::test] +async fn should_fail_for_refund_amount_higher_than_payment_amount() { + let response = CONNECTOR + .make_payment_and_refund( + payment_method_details(), + Some(types::RefundsData { + refund_amount: 150, + ..utils::PaymentRefundType::default().0 + }), + get_default_payment_info(), + ) + .await + .unwrap(); + assert_eq!( + response.response.unwrap_err().message, + "Refund amount (₹1.50) is greater than charge amount (₹1.00)", + ); +} + +// Connector dependent test cases goes here + +// [#478]: add unit tests for non 3DS, wallets & webhooks in connector tests diff --git a/crates/router/tests/connectors/sample_auth.toml b/crates/router/tests/connectors/sample_auth.toml index a1f7919cd7..b712eb2b38 100644 --- a/crates/router/tests/connectors/sample_auth.toml +++ b/crates/router/tests/connectors/sample_auth.toml @@ -218,3 +218,7 @@ api_key="API Key" [zsl] api_key= "Key" key1= "Merchant id" + + +[mifinity] +api_key="API Key" diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index b5c18cfa5d..61ef75ee0f 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -38,6 +38,7 @@ pub struct ConnectorAuthentication { pub gocardless: Option, pub helcim: Option, pub iatapay: Option, + pub mifinity: Option, pub mollie: Option, pub multisafepay: Option, pub netcetera: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 799582c733..892cf94005 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -95,6 +95,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" klarna.base_url = "https://api-na.playground.klarna.com/" +mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" mollie.secondary_base_url = "https://api.cc.mollie.com/v1/" multisafepay.base_url = "https://testapi.multisafepay.com/" @@ -138,7 +139,8 @@ zsl.base_url = "https://api.sitoffalb.net/" apple_pay = { country = "AU,CN,HK,JP,MO,MY,NZ,SG,TW,AM,AT,AZ,BY,BE,BG,HR,CY,CZ,DK,EE,FO,FI,FR,GE,DE,GR,GL,GG,HU,IS,IE,IM,IT,KZ,JE,LV,LI,LT,LU,MT,MD,MC,ME,NL,NO,PL,PT,RO,SM,RS,SK,SI,ES,SE,CH,UA,GB,AR,CO,CR,BR,MX,PE,BH,IL,JO,KW,PS,QA,SA,AE,CA,UM,US,KR,VN,MA,ZA,VA,CL,SV,GT,HN,PA", currency = "AED,AUD,CHF,CAD,EUR,GBP,HKD,SGD,USD" } [connectors.supported] -wallets = ["klarna", "braintree", "applepay"] +wallets = ["klarna", + "mifinity", "braintree", "applepay"] rewards = ["cashtocode", "zen"] cards = [ "aci", diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index ec7d820a13..201f6582dd 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -17306,6 +17306,7 @@ "helcim", "iatapay", "klarna", + "mifinity", "mollie", "multisafepay", "nexinets", diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index d8e789408b..9054712ba1 100755 --- a/scripts/add_connector.sh +++ b/scripts/add_connector.sh @@ -6,7 +6,7 @@ function find_prev_connector() { git checkout $self cp $self $self.tmp # Add new connector to existing list and sort it - connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless helcim iatapay klarna mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1") + connectors=(aci adyen airwallex applepay authorizedotnet bambora bankofamerica billwerk bitpay bluesnap boku braintree cashtocode checkout coinbase cryptopay cybersource dlocal dummyconnector ebanx fiserv forte globalpay globepay gocardless helcim iatapay klarna mifinity mollie multisafepay netcetera nexinets noon nuvei opayo opennode payeezy payme paypal payu placetopay powertranz prophetpay rapyd shift4 square stax stripe threedsecureio trustpay tsys volt wise worldline worldpay zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res=`echo ${sorted[@]}` sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp