diff --git a/api-reference-v2/openapi_spec.json b/api-reference-v2/openapi_spec.json index 197f9fdbda..76b65a1433 100644 --- a/api-reference-v2/openapi_spec.json +++ b/api-reference-v2/openapi_spec.json @@ -8002,6 +8002,7 @@ "threedsecureio", "trustpay", "tsys", + "vgs", "volt", "wellsfargo", "wise", @@ -8235,7 +8236,8 @@ "payment_method_auth", "authentication_processor", "tax_processor", - "billing_processor" + "billing_processor", + "vault_processor" ] }, "ConnectorVolumeSplit": { @@ -14140,6 +14142,14 @@ "type": "object", "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true + }, + "platform_merchant_id": { + "allOf": [ + { + "$ref": "#/components/schemas/id_type.MerchantId" + } + ], + "nullable": true } }, "additionalProperties": false diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index cda884f9de..db90936370 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -9916,6 +9916,7 @@ "threedsecureio", "trustpay", "tsys", + "vgs", "volt", "wellsfargo", "wise", @@ -10130,7 +10131,8 @@ "payment_method_auth", "authentication_processor", "tax_processor", - "billing_processor" + "billing_processor", + "vault_processor" ] }, "ConnectorVolumeSplit": { @@ -16523,6 +16525,14 @@ "type": "object", "description": "Metadata is useful for storing additional, unstructured information on an object.", "nullable": true + }, + "platform_merchant_id": { + "allOf": [ + { + "$ref": "#/components/schemas/id_type.MerchantId" + } + ], + "nullable": true } }, "additionalProperties": false diff --git a/config/config.example.toml b/config/config.example.toml index 1acb5609dd..929a8b5768 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -280,6 +280,7 @@ trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" unified_authentication_service.base_url = "http://localhost:8000" +vgs.base_url = "https://sandbox.vault-api.verygoodvault.com" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" diff --git a/config/development.toml b/config/development.toml index 3a3aeb6440..b0d9d5f4f8 100644 --- a/config/development.toml +++ b/config/development.toml @@ -192,6 +192,7 @@ cards = [ "trustpay", "tsys", "unified_authentication_service", + "vgs", "volt", "wellsfargo", "wellsfargopayout", @@ -331,6 +332,7 @@ xendit.base_url = "https://api.xendit.co" trustpay.base_url = "https://test-tpgw.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" unified_authentication_service.base_url = "http://localhost:8080/" +vgs.base_url = "https://sandbox.vault-api.verygoodvault.com" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index 048330c8dd..46647fd28e 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -211,6 +211,7 @@ trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" unified_authentication_service.base_url = "http://localhost:8000" +vgs.base_url = "https://sandbox.vault-api.verygoodvault.com" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" @@ -310,6 +311,7 @@ cards = [ "trustpay", "tsys", "unified_authentication_service", + "vgs", "volt", "wellsfargo", "wellsfargopayout", diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index ec72060e47..5550d7e23e 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -170,6 +170,21 @@ pub enum BillingConnectors { DummyBillingConnector, } +#[derive(Clone, Copy, Debug, serde::Serialize, strum::EnumString, Eq, PartialEq)] +#[serde(rename_all = "snake_case")] +#[strum(serialize_all = "snake_case")] +pub enum VaultConnectors { + Vgs, +} + +impl From for Connector { + fn from(value: VaultConnectors) -> Self { + match value { + VaultConnectors::Vgs => Self::Vgs, + } + } +} + #[derive( Clone, Debug, serde::Deserialize, serde::Serialize, strum::Display, strum::EnumString, ToSchema, )] @@ -438,6 +453,10 @@ pub fn convert_frm_connector(connector_name: &str) -> Option { FrmConnectors::from_str(connector_name).ok() } +pub fn convert_vault_connector(connector_name: &str) -> Option { + VaultConnectors::from_str(connector_name).ok() +} + #[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, serde::Serialize, Hash)] pub enum ReconPermissionScope { #[serde(rename = "R")] diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index 30bcbaa528..de8d6fe436 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -140,6 +140,7 @@ pub enum RoutableConnectors { // Tsys, Tsys, // UnifiedAuthenticationService, + // Vgs Volt, Wellsfargo, // Wellsfargopayout, @@ -291,6 +292,7 @@ pub enum Connector { Trustpay, Tsys, // UnifiedAuthenticationService, + Vgs, Volt, Wellsfargo, // Wellsfargopayout, @@ -449,6 +451,7 @@ impl Connector { | Self::Trustpay | Self::Tsys // | Self::UnifiedAuthenticationService + | Self::Vgs | Self::Volt | Self::Wellsfargo // | Self::Wellsfargopayout @@ -731,6 +734,7 @@ impl TryFrom for RoutableConnectors { | Connector::Netcetera | Connector::Taxjar | Connector::Threedsecureio + | Connector::Vgs | Connector::CtpVisa => Err("Invalid conversion. Not a routable connector"), } } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 0dd7a1b368..880062cef2 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -465,6 +465,8 @@ pub enum ConnectorType { /// Represents billing processors that handle subscription management, invoicing, /// and recurring payments. Examples include Chargebee, Recurly, and Stripe Billing. BillingProcessor, + /// External Vault Connector + VaultProcessor, } #[derive(Debug, Eq, PartialEq)] diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index b21f65a2fa..2ec4dd140d 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -250,6 +250,7 @@ pub struct ConnectorConfig { pub threedsecureio: Option, pub netcetera: Option, pub tsys: Option, + pub vgs: Option, pub volt: Option, pub wellsfargo: Option, #[cfg(feature = "payouts")] @@ -428,6 +429,7 @@ impl ConnectorConfig { Connector::Threedsecureio => Ok(connector_data.threedsecureio), Connector::Taxjar => Ok(connector_data.taxjar), Connector::Tsys => Ok(connector_data.tsys), + Connector::Vgs => Ok(connector_data.vgs), Connector::Volt => Ok(connector_data.volt), Connector::Wellsfargo => Ok(connector_data.wellsfargo), Connector::Wise => Err("Use get_payout_connector_config".to_string()), diff --git a/crates/hyperswitch_connectors/src/connectors.rs b/crates/hyperswitch_connectors/src/connectors.rs index 99e83b1374..009bec8c92 100644 --- a/crates/hyperswitch_connectors/src/connectors.rs +++ b/crates/hyperswitch_connectors/src/connectors.rs @@ -85,6 +85,7 @@ pub mod thunes; pub mod trustpay; pub mod tsys; pub mod unified_authentication_service; +pub mod vgs; pub mod volt; pub mod wellsfargo; pub mod wellsfargopayout; @@ -116,7 +117,7 @@ pub use self::{ recurly::Recurly, redsys::Redsys, riskified::Riskified, shift4::Shift4, signifyd::Signifyd, square::Square, stax::Stax, stripebilling::Stripebilling, taxjar::Taxjar, threedsecureio::Threedsecureio, thunes::Thunes, trustpay::Trustpay, tsys::Tsys, - unified_authentication_service::UnifiedAuthenticationService, volt::Volt, + unified_authentication_service::UnifiedAuthenticationService, vgs::Vgs, volt::Volt, wellsfargo::Wellsfargo, wellsfargopayout::Wellsfargopayout, wise::Wise, worldline::Worldline, worldpay::Worldpay, worldpayxml::Worldpayxml, xendit::Xendit, zen::Zen, zsl::Zsl, }; diff --git a/crates/hyperswitch_connectors/src/connectors/vgs.rs b/crates/hyperswitch_connectors/src/connectors/vgs.rs new file mode 100644 index 0000000000..4386314e2c --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/vgs.rs @@ -0,0 +1,568 @@ +pub mod transformers; + +use common_utils::{ + errors::CustomResult, + ext_traits::BytesExt, + request::{Method, Request, RequestBuilder, RequestContent}, + types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, +}; +use error_stack::{report, ResultExt}; +use hyperswitch_domain_models::{ + router_data::{AccessToken, ConnectorAuthType, ErrorResponse, RouterData}, + router_flow_types::{ + access_token_auth::AccessTokenAuth, + payments::{Authorize, Capture, PSync, PaymentMethodToken, Session, SetupMandate, Void}, + refunds::{Execute, RSync}, + }, + router_request_types::{ + AccessTokenRequestData, PaymentMethodTokenizationData, PaymentsAuthorizeData, + PaymentsCancelData, PaymentsCaptureData, PaymentsSessionData, PaymentsSyncData, + RefundsData, SetupMandateRequestData, + }, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{ + PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, + RefundSyncRouterData, RefundsRouterData, + }, +}; +use hyperswitch_interfaces::{ + api::{ + self, ConnectorCommon, ConnectorCommonExt, ConnectorIntegration, ConnectorSpecifications, + ConnectorValidation, + }, + configs::Connectors, + errors, + events::connector_api_logs::ConnectorEvent, + types::{self, Response}, + webhooks, +}; +use masking::{ExposeInterface, Mask}; +use transformers as vgs; + +use crate::{constants::headers, types::ResponseRouterData, utils}; + +#[derive(Clone)] +pub struct Vgs { + amount_converter: &'static (dyn AmountConvertor + Sync), +} + +impl Vgs { + pub fn new() -> &'static Self { + &Self { + amount_converter: &StringMinorUnitForConnector, + } + } +} + +impl api::Payment for Vgs {} +impl api::PaymentSession for Vgs {} +impl api::ConnectorAccessToken for Vgs {} +impl api::MandateSetup for Vgs {} +impl api::PaymentAuthorize for Vgs {} +impl api::PaymentSync for Vgs {} +impl api::PaymentCapture for Vgs {} +impl api::PaymentVoid for Vgs {} +impl api::Refund for Vgs {} +impl api::RefundExecute for Vgs {} +impl api::RefundSync for Vgs {} +impl api::PaymentToken for Vgs {} + +impl ConnectorIntegration + for Vgs +{ + // Not Implemented (R) +} + +impl ConnectorCommonExt for Vgs +where + Self: ConnectorIntegration, +{ + fn build_headers( + &self, + 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); + Ok(header) + } +} + +impl ConnectorCommon for Vgs { + fn id(&self) -> &'static str { + "vgs" + } + + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Minor + } + + fn common_get_content_type(&self) -> &'static str { + "application/json" + } + + fn base_url<'a>(&self, connectors: &'a Connectors) -> &'a str { + connectors.vgs.base_url.as_ref() + } + + fn get_auth_header( + &self, + auth_type: &ConnectorAuthType, + ) -> CustomResult)>, errors::ConnectorError> { + let auth = vgs::VgsAuthType::try_from(auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(vec![( + headers::AUTHORIZATION.to_string(), + auth.username.expose().into_masked(), + )]) + } + + fn build_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: vgs::VgsErrorResponse = res + .response + .parse_struct("VgsErrorResponse") + .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, + network_decline_code: None, + network_advice_code: None, + network_error_message: None, + }) + } +} + +impl ConnectorValidation for Vgs { + //TODO: implement functions when support enabled +} + +impl ConnectorIntegration for Vgs { + //TODO: implement sessions flow +} + +impl ConnectorIntegration for Vgs {} + +impl ConnectorIntegration for Vgs {} + +impl ConnectorIntegration for Vgs { + fn get_headers( + &self, + req: &PaymentsAuthorizeRouterData, + 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: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &PaymentsAuthorizeRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let amount = utils::convert_amount( + self.amount_converter, + req.request.minor_amount, + req.request.currency, + )?; + + let connector_router_data = vgs::VgsRouterData::from((amount, req)); + let connector_req = vgs::VgsPaymentsRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &PaymentsAuthorizeRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &PaymentsAuthorizeRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: vgs::VgsPaymentsResponse = res + .response + .parse_struct("Vgs PaymentsAuthorizeResponse") + .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 { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Vgs { + fn get_headers( + &self, + req: &PaymentsSyncRouterData, + 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: &PaymentsSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &PaymentsSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: vgs::VgsPaymentsResponse = res + .response + .parse_struct("vgs PaymentsSyncResponse") + .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 { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Vgs { + fn get_headers( + &self, + req: &PaymentsCaptureRouterData, + 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: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + _req: &PaymentsCaptureRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + } + + fn build_request( + &self, + req: &PaymentsCaptureRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &PaymentsCaptureRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: vgs::VgsPaymentsResponse = res + .response + .parse_struct("Vgs PaymentsCaptureResponse") + .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 { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Vgs {} + +impl ConnectorIntegration for Vgs { + fn get_headers( + &self, + req: &RefundsRouterData, + 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: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn get_request_body( + &self, + req: &RefundsRouterData, + _connectors: &Connectors, + ) -> CustomResult { + let refund_amount = utils::convert_amount( + self.amount_converter, + req.request.minor_refund_amount, + req.request.currency, + )?; + + let connector_router_data = vgs::VgsRouterData::from((refund_amount, req)); + let connector_req = vgs::VgsRefundRequest::try_from(&connector_router_data)?; + Ok(RequestContent::Json(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &RefundsRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + let request = RequestBuilder::new() + .method(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: &RefundsRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult, errors::ConnectorError> { + let response: vgs::RefundResponse = res + .response + .parse_struct("vgs RefundResponse") + .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 { + self.build_error_response(res, event_builder) + } +} + +impl ConnectorIntegration for Vgs { + fn get_headers( + &self, + req: &RefundSyncRouterData, + 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: &RefundSyncRouterData, + _connectors: &Connectors, + ) -> CustomResult { + Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + } + + fn build_request( + &self, + req: &RefundSyncRouterData, + connectors: &Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + RequestBuilder::new() + .method(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: &RefundSyncRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: vgs::RefundResponse = res + .response + .parse_struct("vgs RefundSyncResponse") + .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 { + self.build_error_response(res, event_builder) + } +} + +#[async_trait::async_trait] +impl webhooks::IncomingWebhook for Vgs { + fn get_webhook_object_reference_id( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_event_type( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } + + fn get_webhook_resource_object( + &self, + _request: &webhooks::IncomingWebhookRequestDetails<'_>, + ) -> CustomResult, errors::ConnectorError> { + Err(report!(errors::ConnectorError::WebhooksNotImplemented)) + } +} + +impl ConnectorSpecifications for Vgs {} diff --git a/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs b/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs new file mode 100644 index 0000000000..b4bf6eb520 --- /dev/null +++ b/crates/hyperswitch_connectors/src/connectors/vgs/transformers.rs @@ -0,0 +1,229 @@ +use common_enums::enums; +use common_utils::types::StringMinorUnit; +use hyperswitch_domain_models::{ + payment_method_data::PaymentMethodData, + router_data::{ConnectorAuthType, RouterData}, + router_flow_types::refunds::{Execute, RSync}, + router_request_types::ResponseId, + router_response_types::{PaymentsResponseData, RefundsResponseData}, + types::{PaymentsAuthorizeRouterData, RefundsRouterData}, +}; +use hyperswitch_interfaces::errors; +use masking::Secret; +use serde::{Deserialize, Serialize}; + +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::PaymentsAuthorizeRequestData, +}; + +//TODO: Fill the struct with respective fields +pub struct VgsRouterData { + pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub router_data: T, +} + +impl From<(StringMinorUnit, T)> for VgsRouterData { + fn from((amount, item): (StringMinorUnit, T)) -> Self { + //Todo : use utils to convert the amount to the type of amount that a connector accepts + Self { + amount, + router_data: item, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Serialize, PartialEq)] +pub struct VgsPaymentsRequest { + amount: StringMinorUnit, + card: VgsCard, +} + +#[derive(Default, Debug, Serialize, Eq, PartialEq)] +pub struct VgsCard { + number: cards::CardNumber, + expiry_month: Secret, + expiry_year: Secret, + cvc: Secret, + complete: bool, +} + +impl TryFrom<&VgsRouterData<&PaymentsAuthorizeRouterData>> for VgsPaymentsRequest { + type Error = error_stack::Report; + fn try_from(item: &VgsRouterData<&PaymentsAuthorizeRouterData>) -> Result { + match item.router_data.request.payment_method_data.clone() { + PaymentMethodData::Card(req_card) => { + let card = VgsCard { + 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.clone(), + card, + }) + } + _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), + } + } +} + +//TODO: Fill the struct with respective fields +// Auth Struct +pub struct VgsAuthType { + pub(super) username: Secret, + #[allow(dead_code)] + pub(super) password: Secret, +} + +impl TryFrom<&ConnectorAuthType> for VgsAuthType { + type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { + match auth_type { + ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + username: api_key.to_owned(), + password: key1.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 VgsPaymentStatus { + Succeeded, + Failed, + #[default] + Processing, +} + +impl From for common_enums::AttemptStatus { + fn from(item: VgsPaymentStatus) -> Self { + match item { + VgsPaymentStatus::Succeeded => Self::Charged, + VgsPaymentStatus::Failed => Self::Failure, + VgsPaymentStatus::Processing => Self::Authorizing, + } + } +} + +//TODO: Fill the struct with respective fields +#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct VgsPaymentsResponse { + status: VgsPaymentStatus, + id: String, +} + +impl TryFrom> + for RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: ResponseRouterData, + ) -> Result { + Ok(Self { + status: common_enums::AttemptStatus::from(item.response.status), + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.id), + redirection_data: Box::new(None), + mandate_reference: Box::new(None), + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + incremental_authorization_allowed: None, + charges: None, + }), + ..item.data + }) + } +} + +//TODO: Fill the struct with respective fields +// REFUND : +// Type definition for RefundRequest +#[derive(Default, Debug, Serialize)] +pub struct VgsRefundRequest { + pub amount: StringMinorUnit, +} + +impl TryFrom<&VgsRouterData<&RefundsRouterData>> for VgsRefundRequest { + type Error = error_stack::Report; + fn try_from(item: &VgsRouterData<&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 RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(RefundsResponseData { + connector_refund_id: item.response.id.to_string(), + refund_status: enums::RefundStatus::from(item.response.status), + }), + ..item.data + }) + } +} + +impl TryFrom> for RefundsRouterData { + type Error = error_stack::Report; + fn try_from( + item: RefundsResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(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 VgsErrorResponse { + pub status_code: u16, + pub code: String, + pub message: String, + pub reason: Option, +} diff --git a/crates/hyperswitch_connectors/src/default_implementations.rs b/crates/hyperswitch_connectors/src/default_implementations.rs index 6f20e7222d..3708e02cdc 100644 --- a/crates/hyperswitch_connectors/src/default_implementations.rs +++ b/crates/hyperswitch_connectors/src/default_implementations.rs @@ -116,6 +116,7 @@ macro_rules! default_imp_for_authorize_session_token { } default_imp_for_authorize_session_token!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -228,6 +229,7 @@ macro_rules! default_imp_for_calculate_tax { } default_imp_for_calculate_tax!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -341,6 +343,7 @@ macro_rules! default_imp_for_session_update { } default_imp_for_session_update!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -454,6 +457,7 @@ macro_rules! default_imp_for_post_session_tokens { } default_imp_for_post_session_tokens!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -660,6 +664,7 @@ default_imp_for_update_metadata!( connectors::Tsys, connectors::UnifiedAuthenticationService, connectors::Deutschebank, + connectors::Vgs, connectors::Volt, connectors::Zen, connectors::Zsl, @@ -683,6 +688,7 @@ macro_rules! default_imp_for_complete_authorize { } default_imp_for_complete_authorize!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -777,6 +783,7 @@ macro_rules! default_imp_for_incremental_authorization { } default_imp_for_incremental_authorization!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -890,6 +897,7 @@ macro_rules! default_imp_for_create_customer { } default_imp_for_create_customer!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1004,6 +1012,7 @@ macro_rules! default_imp_for_connector_redirect_response { } default_imp_for_connector_redirect_response!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1098,6 +1107,7 @@ macro_rules! default_imp_for_pre_processing_steps{ } default_imp_for_pre_processing_steps!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Amazonpay, @@ -1200,6 +1210,7 @@ macro_rules! default_imp_for_post_processing_steps{ } default_imp_for_post_processing_steps!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1314,6 +1325,7 @@ macro_rules! default_imp_for_approve { } default_imp_for_approve!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1429,6 +1441,7 @@ macro_rules! default_imp_for_reject { } default_imp_for_reject!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1544,6 +1557,7 @@ macro_rules! default_imp_for_webhook_source_verification { } default_imp_for_webhook_source_verification!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1659,6 +1673,7 @@ macro_rules! default_imp_for_accept_dispute { } default_imp_for_accept_dispute!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -1772,6 +1787,7 @@ macro_rules! default_imp_for_submit_evidence { } default_imp_for_submit_evidence!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -1885,6 +1901,7 @@ macro_rules! default_imp_for_defend_dispute { } default_imp_for_defend_dispute!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -2007,6 +2024,7 @@ macro_rules! default_imp_for_file_upload { } default_imp_for_file_upload!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -2113,6 +2131,7 @@ macro_rules! default_imp_for_payouts { } default_imp_for_payouts!( + connectors::Vgs, connectors::Aci, connectors::Airwallex, connectors::Amazonpay, @@ -2221,6 +2240,7 @@ macro_rules! default_imp_for_payouts_create { #[cfg(feature = "payouts")] default_imp_for_payouts_create!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -2333,6 +2353,7 @@ macro_rules! default_imp_for_payouts_retrieve { #[cfg(feature = "payouts")] default_imp_for_payouts_retrieve!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2448,6 +2469,7 @@ macro_rules! default_imp_for_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_payouts_eligibility!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -2561,6 +2583,7 @@ macro_rules! default_imp_for_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_payouts_fulfill!( + connectors::Vgs, connectors::Aci, connectors::Airwallex, connectors::Amazonpay, @@ -2670,6 +2693,7 @@ macro_rules! default_imp_for_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_payouts_cancel!( + connectors::Vgs, connectors::Aci, connectors::Adyenplatform, connectors::Airwallex, @@ -2783,6 +2807,7 @@ macro_rules! default_imp_for_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_payouts_quote!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2897,6 +2922,7 @@ macro_rules! default_imp_for_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3011,6 +3037,7 @@ macro_rules! default_imp_for_payouts_recipient_account { #[cfg(feature = "payouts")] default_imp_for_payouts_recipient_account!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3127,6 +3154,7 @@ macro_rules! default_imp_for_frm_sale { #[cfg(feature = "frm")] default_imp_for_frm_sale!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3242,6 +3270,7 @@ macro_rules! default_imp_for_frm_checkout { #[cfg(feature = "frm")] default_imp_for_frm_checkout!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3357,6 +3386,7 @@ macro_rules! default_imp_for_frm_transaction { #[cfg(feature = "frm")] default_imp_for_frm_transaction!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3472,6 +3502,7 @@ macro_rules! default_imp_for_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_frm_fulfillment!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3587,6 +3618,7 @@ macro_rules! default_imp_for_frm_record_return { #[cfg(feature = "frm")] default_imp_for_frm_record_return!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3699,6 +3731,7 @@ macro_rules! default_imp_for_revoking_mandates { } default_imp_for_revoking_mandates!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3809,6 +3842,7 @@ macro_rules! default_imp_for_uas_pre_authentication { } default_imp_for_uas_pre_authentication!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -3921,6 +3955,7 @@ macro_rules! default_imp_for_uas_post_authentication { } default_imp_for_uas_post_authentication!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4034,6 +4069,7 @@ macro_rules! default_imp_for_uas_authentication_confirmation { } default_imp_for_uas_authentication_confirmation!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4139,6 +4175,7 @@ macro_rules! default_imp_for_connector_request_id { }; } default_imp_for_connector_request_id!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4246,6 +4283,7 @@ macro_rules! default_imp_for_fraud_check { } #[cfg(feature = "frm")] default_imp_for_fraud_check!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4383,6 +4421,7 @@ macro_rules! default_imp_for_connector_authentication { } default_imp_for_connector_authentication!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4493,6 +4532,7 @@ macro_rules! default_imp_for_uas_authentication { }; } default_imp_for_uas_authentication!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4598,6 +4638,7 @@ macro_rules! default_imp_for_revenue_recovery { } default_imp_for_revenue_recovery! { + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4714,6 +4755,7 @@ macro_rules! default_imp_for_billing_connector_payment_sync { #[cfg(all(feature = "v2", feature = "revenue_recovery"))] default_imp_for_billing_connector_payment_sync!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -4829,6 +4871,7 @@ macro_rules! default_imp_for_revenue_recovery_record_back { #[cfg(all(feature = "v2", feature = "revenue_recovery"))] default_imp_for_revenue_recovery_record_back!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -5035,6 +5078,7 @@ default_imp_for_billing_connector_invoice_sync!( connectors::Worldpay, connectors::Worldpayxml, connectors::Wellsfargo, + connectors::Vgs, connectors::Wellsfargopayout, connectors::Volt, connectors::Xendit, diff --git a/crates/hyperswitch_connectors/src/default_implementations_v2.rs b/crates/hyperswitch_connectors/src/default_implementations_v2.rs index bbe705d33c..3e5d37a44e 100644 --- a/crates/hyperswitch_connectors/src/default_implementations_v2.rs +++ b/crates/hyperswitch_connectors/src/default_implementations_v2.rs @@ -240,6 +240,7 @@ macro_rules! default_imp_for_new_connector_integration_payment { } default_imp_for_new_connector_integration_payment!( + connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, connectors::Adyenplatform, @@ -354,6 +355,7 @@ macro_rules! default_imp_for_new_connector_integration_refund { } default_imp_for_new_connector_integration_refund!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -465,6 +467,7 @@ macro_rules! default_imp_for_new_connector_integration_connector_access_token { } default_imp_for_new_connector_integration_connector_access_token!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -582,6 +585,7 @@ macro_rules! default_imp_for_new_connector_integration_accept_dispute { } default_imp_for_new_connector_integration_accept_dispute!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -697,6 +701,7 @@ macro_rules! default_imp_for_new_connector_integration_submit_evidence { } default_imp_for_new_connector_integration_submit_evidence!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -812,6 +817,7 @@ macro_rules! default_imp_for_new_connector_integration_defend_dispute { } default_imp_for_new_connector_integration_defend_dispute!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -938,6 +944,7 @@ macro_rules! default_imp_for_new_connector_integration_file_upload { } default_imp_for_new_connector_integration_file_upload!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1056,6 +1063,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_create { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_create!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1174,6 +1182,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_eligibility { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_eligibility!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1292,6 +1301,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_fulfill { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_fulfill!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1410,6 +1420,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_cancel { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_cancel!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1528,6 +1539,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_quote { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_quote!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1646,6 +1658,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1764,6 +1777,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_sync { #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_sync!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1882,6 +1896,7 @@ macro_rules! default_imp_for_new_connector_integration_payouts_recipient_account #[cfg(feature = "payouts")] default_imp_for_new_connector_integration_payouts_recipient_account!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -1998,6 +2013,7 @@ macro_rules! default_imp_for_new_connector_integration_webhook_source_verificati } default_imp_for_new_connector_integration_webhook_source_verification!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2116,6 +2132,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_sale { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_sale!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2234,6 +2251,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_checkout { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_checkout!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2352,6 +2370,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_transaction { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_transaction!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2470,6 +2489,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_fulfillment { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_fulfillment!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2588,6 +2608,7 @@ macro_rules! default_imp_for_new_connector_integration_frm_record_return { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm_record_return!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2703,6 +2724,7 @@ macro_rules! default_imp_for_new_connector_integration_revoking_mandates { } default_imp_for_new_connector_integration_revoking_mandates!( + connectors::Vgs, connectors::Aci, connectors::Adyen, connectors::Adyenplatform, @@ -2812,6 +2834,7 @@ macro_rules! default_imp_for_new_connector_integration_frm { #[cfg(feature = "frm")] default_imp_for_new_connector_integration_frm!( + connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -2927,6 +2950,7 @@ macro_rules! default_imp_for_new_connector_integration_connector_authentication } default_imp_for_new_connector_integration_connector_authentication!( + connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, @@ -3031,6 +3055,7 @@ macro_rules! default_imp_for_new_connector_integration_revenue_recovery { } default_imp_for_new_connector_integration_revenue_recovery!( + connectors::Vgs, connectors::Airwallex, connectors::Amazonpay, connectors::Bambora, diff --git a/crates/hyperswitch_domain_models/src/configs.rs b/crates/hyperswitch_domain_models/src/configs.rs index 5ebf77a542..afbf9dc16a 100644 --- a/crates/hyperswitch_domain_models/src/configs.rs +++ b/crates/hyperswitch_domain_models/src/configs.rs @@ -103,6 +103,7 @@ pub struct Connectors { pub trustpay: ConnectorParamsWithMoreUrls, pub tsys: ConnectorParams, pub unified_authentication_service: ConnectorParams, + pub vgs: ConnectorParams, pub volt: ConnectorParams, pub wellsfargo: ConnectorParams, pub wellsfargopayout: ConnectorParams, diff --git a/crates/router/src/connector.rs b/crates/router/src/connector.rs index fcf751b9d1..51482cb09a 100644 --- a/crates/router/src/connector.rs +++ b/crates/router/src/connector.rs @@ -34,9 +34,9 @@ pub use hyperswitch_connectors::connectors::{ square::Square, stax, stax::Stax, stripebilling, stripebilling::Stripebilling, taxjar, taxjar::Taxjar, threedsecureio, threedsecureio::Threedsecureio, thunes, thunes::Thunes, trustpay, trustpay::Trustpay, tsys, tsys::Tsys, unified_authentication_service, - unified_authentication_service::UnifiedAuthenticationService, volt, volt::Volt, wellsfargo, - wellsfargo::Wellsfargo, wellsfargopayout, wellsfargopayout::Wellsfargopayout, wise, wise::Wise, - worldline, worldline::Worldline, worldpay, worldpay::Worldpay, worldpayxml, + unified_authentication_service::UnifiedAuthenticationService, vgs, vgs::Vgs, volt, volt::Volt, + wellsfargo, wellsfargo::Wellsfargo, wellsfargopayout, wellsfargopayout::Wellsfargopayout, wise, + wise::Wise, worldline, worldline::Worldline, worldpay, worldpay::Worldpay, worldpayxml, worldpayxml::Worldpayxml, xendit, xendit::Xendit, zen, zen::Zen, zsl, zsl::Zsl, }; diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 723f732803..f12dc48924 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1291,6 +1291,10 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { use crate::connector::*; match self.connector_name { + api_enums::Connector::Vgs => { + vgs::transformers::VgsAuthType::try_from(self.auth_type)?; + Ok(()) + } api_enums::Connector::Adyenplatform => { adyenplatform::transformers::AdyenplatformAuthType::try_from(self.auth_type)?; Ok(()) @@ -1925,6 +1929,8 @@ impl ConnectorTypeAndConnectorName<'_> { let mut routable_connector = api_enums::RoutableConnectors::from_str(&self.connector_name.to_string()).ok(); + let vault_connector = + api_enums::convert_vault_connector(self.connector_name.to_string().as_str()); let pm_auth_connector = api_enums::convert_pm_auth_connector(self.connector_name.to_string().as_str()); let authentication_connector = @@ -1964,6 +1970,13 @@ impl ConnectorTypeAndConnectorName<'_> { } .into()); } + } else if vault_connector.is_some() { + if self.connector_type != &api_enums::ConnectorType::VaultProcessor { + return Err(errors::ApiErrorResponse::InvalidRequestData { + message: "Invalid connector type given".to_string(), + } + .into()); + } } else { let routable_connector_option = self .connector_name diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index caff3c707f..eebff72722 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -556,6 +556,7 @@ impl ConnectorData { // enums::Connector::UnifiedAuthenticationService => Ok(ConnectorEnum::Old(Box::new( // connector::UnifiedAuthenticationService, // ))), + enums::Connector::Vgs => Ok(ConnectorEnum::Old(Box::new(connector::Vgs::new()))), enums::Connector::Volt => Ok(ConnectorEnum::Old(Box::new(connector::Volt::new()))), enums::Connector::Wellsfargo => { Ok(ConnectorEnum::Old(Box::new(connector::Wellsfargo::new()))) diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index e171aa5a59..9af28d4adc 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -325,6 +325,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { // api_enums::Connector::UnifiedAuthenticationService => { // Self::UnifiedAuthenticationService // } + // api_enums::Connector::Vgs => Self::Vgs, api_enums::Connector::Volt => Self::Volt, api_enums::Connector::Wellsfargo => Self::Wellsfargo, // api_enums::Connector::Wellsfargopayout => Self::Wellsfargopayout, @@ -365,6 +366,11 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { message: "Taxjar is not a routable connector".to_string(), })? } + api_enums::Connector::Vgs => { + Err(common_utils::errors::ValidationError::InvalidValue { + message: "Vgs is not a routable connector".to_string(), + })? + } }) } } diff --git a/crates/router/tests/connectors/main.rs b/crates/router/tests/connectors/main.rs index b2e546a157..144f464b9d 100644 --- a/crates/router/tests/connectors/main.rs +++ b/crates/router/tests/connectors/main.rs @@ -92,6 +92,7 @@ mod trustpay; mod tsys; mod unified_authentication_service; mod utils; +mod vgs; mod volt; mod wellsfargo; // mod wellsfargopayout; diff --git a/crates/router/tests/connectors/vgs.rs b/crates/router/tests/connectors/vgs.rs new file mode 100644 index 0000000000..fb9b6bb30b --- /dev/null +++ b/crates/router/tests/connectors/vgs.rs @@ -0,0 +1,420 @@ +use masking::Secret; +use router::types::{self, domain, storage::enums}; +use test_utils::connector_auth; + +use crate::utils::{self, ConnectorActions}; + +#[derive(Clone, Copy)] +struct VgsTest; +impl ConnectorActions for VgsTest {} +impl utils::Connector for VgsTest { + fn get_data(&self) -> types::api::ConnectorData { + use router::connector::Vgs; + utils::construct_connector_data_old( + Box::new(Vgs::new()), + types::Connector::Vgs, + types::api::GetToken::Connector, + None, + ) + } + + fn get_auth_token(&self) -> types::ConnectorAuthType { + utils::to_connector_auth_type( + connector_auth::ConnectorAuthentication::new() + .vgs + .expect("Missing connector authentication configuration") + .into(), + ) + } + + fn get_name(&self) -> String { + "vgs".to_string() + } +} + +static CONNECTOR: VgsTest = VgsTest {}; + +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: 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: 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: 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: 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: 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/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index 6db09af0c7..da93a4720c 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -100,6 +100,7 @@ pub struct ConnectorAuthentication { pub trustpay: Option, pub tsys: Option, pub unified_authentication_service: Option, + pub vgs: Option, pub volt: Option, pub wellsfargo: Option, // pub wellsfargopayout: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 4dff7cedbe..745dc71dd2 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -178,6 +178,7 @@ trustpay.base_url = "https://test-tpgw.trustpay.eu/" trustpay.base_url_bank_redirects = "https://aapi.trustpay.eu/" tsys.base_url = "https://stagegw.transnox.com/" unified_authentication_service.base_url = "http://localhost:8000" +vgs.base_url = "https://sandbox.vault-api.verygoodvault.com" volt.base_url = "https://api.sandbox.volt.io/" wellsfargo.base_url = "https://apitest.cybersource.com/" wellsfargopayout.base_url = "https://api-sandbox.wellsfargo.com/" @@ -276,6 +277,7 @@ cards = [ "trustpay", "tsys", "unified_authentication_service", + "vgs", "volt", "wellsfargo", "wellsfargopayout", diff --git a/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/down.sql b/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/down.sql new file mode 100644 index 0000000000..da13010c01 --- /dev/null +++ b/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +select 1; \ No newline at end of file diff --git a/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/up.sql b/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/up.sql new file mode 100644 index 0000000000..0c5de050d6 --- /dev/null +++ b/migrations/2025-05-08-062918_add-vault-processor-in-connector-type/up.sql @@ -0,0 +1,3 @@ +-- Your SQL goes here +ALTER TYPE "ConnectorType" +ADD VALUE IF NOT EXISTS 'vault_processor'; \ No newline at end of file diff --git a/scripts/add_connector.sh b/scripts/add_connector.sh index 9a4a7ccbfa..72a0f3543f 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 adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon facilitapay fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim hipay iatapay inespay itaubank jpmorgan juspaythreedsserver klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys shift4 square stax stripe stripebilling taxjar threedsecureio thunes trustpay tsys unified_authentication_service volt wellsfargo wellsfargopayout wise worldline worldpay worldpayxml xendit zsl "$1") + connectors=(aci adyen adyenplatform airwallex amazonpay applepay authorizedotnet bambora bamboraapac bankofamerica billwerk bitpay bluesnap boku braintree cashtocode chargebee checkout coinbase cryptopay ctp_visa cybersource datatrans deutschebank digitalvirgo dlocal dummyconnector ebanx elavon facilitapay fiserv fiservemea fiuu forte getnet globalpay globepay gocardless gpayments helcim hipay iatapay inespay itaubank jpmorgan juspaythreedsserver klarna mifinity mollie moneris multisafepay netcetera nexinets nexixpay nomupay noon novalnet nuvei opayo opennode paybox payeezy payme payone paypal paystack payu placetopay plaid powertranz prophetpay rapyd razorpay recurly redsys shift4 square stax stripe stripebilling taxjar threedsecureio thunes trustpay tsys unified_authentication_service vgs volt wellsfargo wellsfargopayout wise worldline worldpay worldpayxml xendit zsl "$1") IFS=$'\n' sorted=($(sort <<<"${connectors[*]}")); unset IFS res="$(echo ${sorted[@]})" sed -i'' -e "s/^ connectors=.*/ connectors=($res \"\$1\")/" $self.tmp