diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 561c5e75d1..b61823fcb4 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -41,7 +41,6 @@ crates/analytics/ @juspay/hyperswitch-analytics scripts/add_connector.sh @juspay/hyperswitch-connector connector-template/ @juspay/hyperswitch-connector -crates/router/src/core/connector_validation.rs @juspay/hyperswitch-connector crates/router/src/connector/ @juspay/hyperswitch-connector crates/router/tests/connectors/ @juspay/hyperswitch-connector crates/test_utils/tests/connectors/ @juspay/hyperswitch-connector @@ -59,6 +58,7 @@ crates/router/src/types/connector_transformers.rs @juspay/hyperswitch-connector crates/router/src/compatibility/ @juspay/hyperswitch-compatibility crates/router/src/core/ @juspay/hyperswitch-core +crates/router/src/core/connector_validation.rs @juspay/hyperswitch-connector crates/api_models/src/routing.rs @juspay/hyperswitch-routing crates/hyperswitch_constraint_graph @juspay/hyperswitch-routing diff --git a/api-reference/v1/openapi_spec_v1.json b/api-reference/v1/openapi_spec_v1.json index 2a57fdbb45..bd1d8887c4 100644 --- a/api-reference/v1/openapi_spec_v1.json +++ b/api-reference/v1/openapi_spec_v1.json @@ -12077,6 +12077,7 @@ "flexiti", "forte", "getnet", + "gigadat", "globalpay", "globepay", "gocardless", @@ -30267,6 +30268,7 @@ "flexiti", "forte", "getnet", + "gigadat", "globalpay", "globepay", "gocardless", diff --git a/api-reference/v2/openapi_spec_v2.json b/api-reference/v2/openapi_spec_v2.json index 7412733306..973b3391e2 100644 --- a/api-reference/v2/openapi_spec_v2.json +++ b/api-reference/v2/openapi_spec_v2.json @@ -8684,6 +8684,7 @@ "flexiti", "forte", "getnet", + "gigadat", "globalpay", "globepay", "gocardless", @@ -23938,6 +23939,7 @@ "flexiti", "forte", "getnet", + "gigadat", "globalpay", "globepay", "gocardless", diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index 5f8f8b4a62..fdcb7891f3 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -762,6 +762,10 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.gigadat] +interac = { currency = "CAD"} + [payout_method_filters.adyenplatform] sepa = { country = "AT,BE,CH,CZ,DE,EE,ES,FI,FR,GB,HU,IE,IT,LT,LV,NL,NO,PL,PT,SE,SK", currency = "EUR,CZK,DKK,HUF,NOK,PLN,SEK,GBP,CHF" } credit = { country = "AT,BE,BG,CY,CZ,DE,DK,EE,ES,FI,FR,GB,GR,HR,HU,IE,IS,IT,LI,LT,LU,LV,MT,NL,NO,PL,PT,RO,SE,SI,SK,US", currency = "EUR,USD,GBP" } diff --git a/config/deployments/production.toml b/config/deployments/production.toml index 491ca6a9ea..552a6ebb0e 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -786,6 +786,10 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.gigadat] +interac = { currency = "CAD"} + [pm_filters.coingate] crypto_currency = { country = "AL, AD, AT, BE, BA, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IS, IE, IT, LV, LT, LU, MT, MD, NL, NO, PL, PT, RO, RS, SK, SI, ES, SE, CH, UA, GB, AR, BR, CL, CO, CR, DO, SV, GD, MX, PE, LC, AU, NZ, CY, HK, IN, IL, JP, KR, QA, SA, SG, EG", currency = "EUR, USD, GBP" } diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index b25eb5ff62..520883d555 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -798,6 +798,9 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } +[pm_filters.gigadat] +interac = { currency = "CAD"} + [pm_filters.coingate] crypto_currency = { country = "AL, AD, AT, BE, BA, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IS, IE, IT, LV, LT, LU, MT, MD, NL, NO, PL, PT, RO, RS, SK, SI, ES, SE, CH, UA, GB, AR, BR, CL, CO, CR, DO, SV, GD, MX, PE, LC, AU, NZ, CY, HK, IN, IL, JP, KR, QA, SA, SG, EG", currency = "EUR, USD, GBP" } diff --git a/config/development.toml b/config/development.toml index 15a0348d7d..3bab0fc27b 100644 --- a/config/development.toml +++ b/config/development.toml @@ -778,6 +778,8 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } +[pm_filters.gigadat] +interac = { currency = "CAD"} [pm_filters.mollie] credit = { not_available_flows = { capture_method = "manual" } } diff --git a/config/docker_compose.toml b/config/docker_compose.toml index ef0ca21ebf..fa082b3552 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -929,6 +929,9 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } +[pm_filters.gigadat] +interac = { currency = "CAD"} + [pm_filters.coingate] crypto_currency = { country = "AL, AD, AT, BE, BA, BG, HR, CZ, DK, EE, FI, FR, DE, GR, HU, IS, IE, IT, LV, LT, LU, MT, MD, NL, NO, PL, PT, RO, RS, SK, SI, ES, SE, CH, UA, GB, AR, BR, CL, CO, CR, DO, SV, GD, MX, PE, LC, AU, NZ, CY, HK, IN, IL, JP, KR, QA, SA, SG, EG", currency = "EUR, USD, GBP" } diff --git a/crates/common_enums/src/connector_enums.rs b/crates/common_enums/src/connector_enums.rs index e2832a6b95..a4369cd786 100644 --- a/crates/common_enums/src/connector_enums.rs +++ b/crates/common_enums/src/connector_enums.rs @@ -102,6 +102,7 @@ pub enum RoutableConnectors { Flexiti, Forte, Getnet, + Gigadat, Globalpay, Globepay, Gocardless, @@ -277,6 +278,7 @@ pub enum Connector { Flexiti, Forte, Getnet, + Gigadat, Globalpay, Globepay, Gocardless, @@ -475,6 +477,7 @@ impl Connector { | Self::Flexiti | Self::Forte | Self::Getnet + | Self::Gigadat | Self::Globalpay | Self::Globepay | Self::Gocardless @@ -660,6 +663,7 @@ impl From for Connector { RoutableConnectors::Flexiti => Self::Flexiti, RoutableConnectors::Forte => Self::Forte, RoutableConnectors::Getnet => Self::Getnet, + RoutableConnectors::Gigadat => Self::Gigadat, RoutableConnectors::Globalpay => Self::Globalpay, RoutableConnectors::Globepay => Self::Globepay, RoutableConnectors::Gocardless => Self::Gocardless, @@ -857,6 +861,7 @@ impl TryFrom for RoutableConnectors { Connector::Zsl => Ok(Self::Zsl), Connector::Recurly => Ok(Self::Recurly), Connector::Getnet => Ok(Self::Getnet), + Connector::Gigadat => Ok(Self::Gigadat), Connector::Hipay => Ok(Self::Hipay), Connector::Inespay => Ok(Self::Inespay), Connector::Redsys => Ok(Self::Redsys), diff --git a/crates/connector_configs/src/common_config.rs b/crates/connector_configs/src/common_config.rs index be97726a19..97703c5453 100644 --- a/crates/connector_configs/src/common_config.rs +++ b/crates/connector_configs/src/common_config.rs @@ -111,6 +111,7 @@ pub struct ApiModelMetaData { pub tenant_id: Option, pub platform_url: Option, pub account_id: Option, + pub site: Option, } #[serde_with::skip_serializing_none] diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 6db43c3174..fec33f7a09 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -186,6 +186,7 @@ pub struct ConfigMetadata { pub route: Option, pub mid: Option, pub tid: Option, + pub site: Option, } #[serde_with::skip_serializing_none] @@ -495,6 +496,7 @@ impl ConnectorConfig { Connector::Flexiti => Ok(connector_data.flexiti), Connector::Forte => Ok(connector_data.forte), Connector::Getnet => Ok(connector_data.getnet), + Connector::Gigadat => Ok(connector_data.gigadat), Connector::Globalpay => Ok(connector_data.globalpay), Connector::Globepay => Ok(connector_data.globepay), Connector::Gocardless => Ok(connector_data.gocardless), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index 0871692caa..06324d681b 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -7166,5 +7166,15 @@ api_key = "API Key" key1 = "TokenEx ID" [gigadat] -[gigadat.connector_auth.HeaderKey] -api_key = "API Key" +[gigadat.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Compaign Id" +[[gigadat.bank_redirect]] +payment_method_type = "interac" +[gigadat.metadata.site] +name = "site" +label = "Site where transaction is initiated" +placeholder = "Enter site where transaction is initiated" +required = true +type = "Text" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 66f84be371..9f48199919 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -5903,5 +5903,15 @@ type="Text" api_key = "API Key" [gigadat] -[gigadat.connector_auth.HeaderKey] -api_key = "API Key" +[gigadat.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Compaign Id" +[[gigadat.bank_redirect]] +payment_method_type = "interac" +[gigadat.metadata.site] +name = "site" +label = "Site where transaction is initiated" +placeholder = "Enter site where transaction is initiated" +required = true +type = "Text" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index c5790d9672..9084f0c763 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -7144,5 +7144,15 @@ type="Text" api_key = "API Key" [gigadat] -[gigadat.connector_auth.HeaderKey] -api_key = "API Key" +[gigadat.connector_auth.SignatureKey] +api_key = "Username" +api_secret = "Password" +key1 = "Compaign Id" +[[gigadat.bank_redirect]] +payment_method_type = "interac" +[gigadat.metadata.site] +name = "site" +label = "Site where transaction is initiated" +placeholder = "Enter site where transaction is initiated" +required = true +type = "Text" diff --git a/crates/hyperswitch_connectors/src/connectors/gigadat.rs b/crates/hyperswitch_connectors/src/connectors/gigadat.rs index bebad38b04..6b566ef3be 100644 --- a/crates/hyperswitch_connectors/src/connectors/gigadat.rs +++ b/crates/hyperswitch_connectors/src/connectors/gigadat.rs @@ -1,13 +1,13 @@ pub mod transformers; -use std::sync::LazyLock; - +use base64::Engine; use common_enums::enums; use common_utils::{ + consts, errors::CustomResult, ext_traits::BytesExt, request::{Method, Request, RequestBuilder, RequestContent}, - types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}, + types::{AmountConvertor, FloatMajorUnit, FloatMajorUnitForConnector}, }; use error_stack::{report, ResultExt}; use hyperswitch_domain_models::{ @@ -24,11 +24,12 @@ use hyperswitch_domain_models::{ RefundsData, SetupMandateRequestData, }, router_response_types::{ - ConnectorInfo, PaymentsResponseData, RefundsResponseData, SupportedPaymentMethods, + ConnectorInfo, PaymentMethodDetails, PaymentsResponseData, RefundsResponseData, + SupportedPaymentMethods, SupportedPaymentMethodsExt, }, types::{ PaymentsAuthorizeRouterData, PaymentsCaptureRouterData, PaymentsSyncRouterData, - RefundSyncRouterData, RefundsRouterData, + RefundsRouterData, }, }; use hyperswitch_interfaces::{ @@ -37,25 +38,27 @@ use hyperswitch_interfaces::{ ConnectorValidation, }, configs::Connectors, - errors, + consts as api_consts, errors, events::connector_api_logs::ConnectorEvent, types::{self, Response}, webhooks, }; -use masking::{ExposeInterface, Mask}; +use lazy_static::lazy_static; +use masking::{Mask, PeekInterface}; use transformers as gigadat; +use uuid::Uuid; use crate::{constants::headers, types::ResponseRouterData, utils}; #[derive(Clone)] pub struct Gigadat { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Gigadat { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &FloatMajorUnitForConnector, } } } @@ -121,9 +124,11 @@ impl ConnectorCommon for Gigadat { ) -> CustomResult)>, errors::ConnectorError> { let auth = gigadat::GigadatAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_key = format!("{}:{}", auth.username.peek(), auth.password.peek()); + let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); Ok(vec![( headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), + auth_header.into_masked(), )]) } @@ -142,9 +147,9 @@ impl ConnectorCommon for Gigadat { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.err.clone(), + message: response.err.clone(), + reason: Some(response.err).clone(), attempt_status: None, connector_transaction_id: None, network_advice_code: None, @@ -204,10 +209,16 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let auth = gigadat::GigadatAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + Ok(format!( + "{}api/payment-token/{}", + self.base_url(connectors), + auth.campaign_id.peek() + )) } fn get_request_body( @@ -222,7 +233,7 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: gigadat::GigadatPaymentsResponse = res + let response: gigadat::GigadatPaymentResponse = res .response - .parse_struct("Gigadat PaymentsAuthorizeResponse") + .parse_struct("GigadatPaymentResponse") .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(), @@ -291,10 +304,18 @@ impl ConnectorIntegration for Gig fn get_url( &self, - _req: &PaymentsSyncRouterData, - _connectors: &Connectors, + req: &PaymentsSyncRouterData, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let transaction_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + Ok(format!( + "{}api/transactions/{transaction_id}", + self.base_url(connectors) + )) } fn build_request( @@ -318,7 +339,7 @@ impl ConnectorIntegration for Gig event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: gigadat::GigadatPaymentsResponse = res + let response: gigadat::GigadatTransactionStatusResponse = res .response .parse_struct("gigadat PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -395,7 +416,7 @@ impl ConnectorIntegration fo event_builder: Option<&mut ConnectorEvent>, res: Response, ) -> CustomResult { - let response: gigadat::GigadatPaymentsResponse = res + let response: gigadat::GigadatTransactionStatusResponse = res .response .parse_struct("Gigadat PaymentsCaptureResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; @@ -423,9 +444,22 @@ impl ConnectorIntegration for Gigadat fn get_headers( &self, req: &RefundsRouterData, - connectors: &Connectors, + _connectors: &Connectors, ) -> CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let auth = gigadat::GigadatAuthType::try_from(&req.connector_auth_type) + .change_context(errors::ConnectorError::FailedToObtainAuthType)?; + let auth_key = format!("{}:{}", auth.username.peek(), auth.password.peek()); + let auth_header = format!("Basic {}", consts::BASE64_ENGINE.encode(auth_key)); + Ok(vec![ + ( + headers::AUTHORIZATION.to_string(), + auth_header.into_masked(), + ), + ( + headers::IDEMPOTENCY_KEY.to_string(), + Uuid::new_v4().to_string().into_masked(), + ), + ]) } fn get_content_type(&self) -> &'static str { @@ -435,9 +469,9 @@ impl ConnectorIntegration for Gigadat fn get_url( &self, _req: &RefundsRouterData, - _connectors: &Connectors, + connectors: &Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}refunds", self.base_url(connectors),)) } fn get_request_body( @@ -499,75 +533,41 @@ impl ConnectorIntegration for Gigadat res: Response, event_builder: Option<&mut ConnectorEvent>, ) -> CustomResult { - self.build_error_response(res, event_builder) + let response: gigadat::GigadatRefundErrorResponse = res + .response + .parse_struct("GigadatRefundErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + let code = response + .error + .first() + .and_then(|error_detail| error_detail.code.clone()) + .unwrap_or(api_consts::NO_ERROR_CODE.to_string()); + let message = response + .error + .first() + .map(|error_detail| error_detail.detail.clone()) + .unwrap_or(api_consts::NO_ERROR_MESSAGE.to_string()); + Ok(ErrorResponse { + status_code: res.status_code, + code, + message, + reason: Some(response.message).clone(), + attempt_status: None, + connector_transaction_id: None, + network_advice_code: None, + network_decline_code: None, + network_error_message: None, + connector_metadata: None, + }) } } impl ConnectorIntegration for Gigadat { - 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: gigadat::RefundResponse = res - .response - .parse_struct("gigadat 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) - } + //Gigadat does not support Refund Sync } #[async_trait::async_trait] @@ -594,21 +594,36 @@ impl webhooks::IncomingWebhook for Gigadat { } } -static GIGADAT_SUPPORTED_PAYMENT_METHODS: LazyLock = - LazyLock::new(SupportedPaymentMethods::new); +lazy_static! { + static ref GIGADAT_SUPPORTED_PAYMENT_METHODS: SupportedPaymentMethods = { + let supported_capture_methods = vec![enums::CaptureMethod::Automatic]; -static GIGADAT_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { - display_name: "Gigadat", - description: "Gigadat connector", - connector_type: enums::HyperswitchConnectorCategory::PaymentGateway, - integration_status: enums::ConnectorIntegrationStatus::Sandbox, -}; + let mut gigadat_supported_payment_methods = SupportedPaymentMethods::new(); + gigadat_supported_payment_methods.add( + enums::PaymentMethod::BankRedirect, + enums::PaymentMethodType::Interac, + PaymentMethodDetails { + mandates: common_enums::FeatureStatus::NotSupported, + refunds: common_enums::FeatureStatus::Supported, + supported_capture_methods, + specific_features: None, + }, + ); -static GIGADAT_SUPPORTED_WEBHOOK_FLOWS: [enums::EventClass; 0] = []; + gigadat_supported_payment_methods + }; + static ref GIGADAT_CONNECTOR_INFO: ConnectorInfo = ConnectorInfo { + display_name: "Gigadat", + description: "Gigadat is a financial services product that offers a single API for payment integration. It provides Canadian businesses with a secure payment gateway and various pay-in and pay-out solutions, including Interac e-Transfer", + connector_type: enums::HyperswitchConnectorCategory::PaymentGateway, + integration_status: enums::ConnectorIntegrationStatus::Sandbox, + }; + static ref GIGADAT_SUPPORTED_WEBHOOK_FLOWS: Vec = Vec::new(); +} impl ConnectorSpecifications for Gigadat { fn get_connector_about(&self) -> Option<&'static ConnectorInfo> { - Some(&GIGADAT_CONNECTOR_INFO) + Some(&*GIGADAT_CONNECTOR_INFO) } fn get_supported_payment_methods(&self) -> Option<&'static SupportedPaymentMethods> { @@ -616,6 +631,6 @@ impl ConnectorSpecifications for Gigadat { } fn get_supported_webhook_flows(&self) -> Option<&'static [enums::EventClass]> { - Some(&GIGADAT_SUPPORTED_WEBHOOK_FLOWS) + Some(&*GIGADAT_SUPPORTED_WEBHOOK_FLOWS) } } diff --git a/crates/hyperswitch_connectors/src/connectors/gigadat/transformers.rs b/crates/hyperswitch_connectors/src/connectors/gigadat/transformers.rs index db58300ed3..d87bb6374e 100644 --- a/crates/hyperswitch_connectors/src/connectors/gigadat/transformers.rs +++ b/crates/hyperswitch_connectors/src/connectors/gigadat/transformers.rs @@ -1,28 +1,35 @@ -use common_enums::enums; -use common_utils::types::StringMinorUnit; +use common_enums::{enums, Currency}; +use common_utils::{ + id_type, + pii::{self, Email, IpAddress}, + request::Method, + types::FloatMajorUnit, +}; +use error_stack::ResultExt; use hyperswitch_domain_models::{ - payment_method_data::PaymentMethodData, + payment_method_data::{BankRedirectData, PaymentMethodData}, router_data::{ConnectorAuthType, RouterData}, - router_flow_types::refunds::{Execute, RSync}, + router_flow_types::refunds::Execute, router_request_types::ResponseId, - router_response_types::{PaymentsResponseData, RefundsResponseData}, + router_response_types::{PaymentsResponseData, RedirectForm, RefundsResponseData}, types::{PaymentsAuthorizeRouterData, RefundsRouterData}, }; use hyperswitch_interfaces::errors; -use masking::Secret; +use masking::{PeekInterface, Secret}; use serde::{Deserialize, Serialize}; -use crate::types::{RefundsResponseRouterData, ResponseRouterData}; +use crate::{ + types::{RefundsResponseRouterData, ResponseRouterData}, + utils::{self, BrowserInformationData, PaymentsAuthorizeRequestData, RouterData as _}, +}; -//TODO: Fill the struct with respective fields pub struct GigadatRouterData { - pub amount: StringMinorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. + pub amount: FloatMajorUnit, // The type of amount that a connector accepts, for example, String, i64, f64, etc. pub router_data: T, } -impl From<(StringMinorUnit, T)> for GigadatRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { - //Todo : use utils to convert the amount to the type of amount that a connector accepts +impl From<(FloatMajorUnit, T)> for GigadatRouterData { + fn from((amount, item): (FloatMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -30,93 +37,214 @@ impl From<(StringMinorUnit, T)> for GigadatRouterData { } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, PartialEq)] -pub struct GigadatPaymentsRequest { - amount: StringMinorUnit, - card: GigadatCard, +const CONNECTOR_BASE_URL: &str = "https://interac.express-connect.com/"; + +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct GigadatConnectorMetadataObject { + pub site: String, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct GigadatCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +impl TryFrom<&Option> for GigadatConnectorMetadataObject { + type Error = error_stack::Report; + fn try_from(meta_data: &Option) -> Result { + let metadata: Self = utils::to_connector_meta_from_secret::(meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "merchant_connector_account.metadata", + })?; + Ok(metadata) + } } -impl TryFrom<&GigadatRouterData<&PaymentsAuthorizeRouterData>> for GigadatPaymentsRequest { +// CPI (Combined Pay-in) Request Structure for Gigadat +#[derive(Debug, Serialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct GigadatCpiRequest { + pub user_id: id_type::CustomerId, + pub site: String, + pub user_ip: Secret, + pub currency: Currency, + pub amount: FloatMajorUnit, + pub transaction_id: String, + #[serde(rename = "type")] + pub transaction_type: GidadatTransactionType, + pub name: Secret, + pub email: Email, + pub mobile: Secret, +} + +#[derive(Debug, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "UPPERCASE")] +pub enum GidadatTransactionType { + Cpi, +} + +impl TryFrom<&GigadatRouterData<&PaymentsAuthorizeRouterData>> for GigadatCpiRequest { type Error = error_stack::Report; fn try_from( item: &GigadatRouterData<&PaymentsAuthorizeRouterData>, ) -> Result { + let metadata: GigadatConnectorMetadataObject = + utils::to_connector_meta_from_secret(item.router_data.connector_meta_data.clone()) + .change_context(errors::ConnectorError::InvalidConnectorConfig { + config: "merchant_connector_account.metadata", + })?; match item.router_data.request.payment_method_data.clone() { - PaymentMethodData::Card(_) => Err(errors::ConnectorError::NotImplemented( - "Card payment method not implemented".to_string(), + PaymentMethodData::BankRedirect(BankRedirectData::Interac { .. }) => { + let router_data = item.router_data; + let name = router_data.get_billing_full_name()?; + let email = router_data.get_billing_email()?; + let mobile = router_data.get_billing_phone_number()?; + let currency = item.router_data.request.currency; + + let user_ip = router_data.request.get_browser_info()?.get_ip_address()?; + + Ok(Self { + user_id: router_data.get_customer_id()?, + site: metadata.site, + user_ip, + currency, + amount: item.amount, + transaction_id: router_data.connector_request_reference_id.clone(), + transaction_type: GidadatTransactionType::Cpi, + name, + email, + mobile, + }) + } + PaymentMethodData::BankRedirect(_) => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Gigadat"), + ))?, + + _ => Err(errors::ConnectorError::NotImplemented( + utils::get_unimplemented_payment_method_error_message("Gigadat"), ) .into()), - _ => Err(errors::ConnectorError::NotImplemented("Payment method".to_string()).into()), } } } -//TODO: Fill the struct with respective fields -// Auth Struct +#[derive(Debug, Clone)] pub struct GigadatAuthType { - pub(super) api_key: Secret, + pub campaign_id: Secret, + pub username: Secret, + pub password: Secret, } impl TryFrom<&ConnectorAuthType> for GigadatAuthType { type Error = error_stack::Report; + fn try_from(auth_type: &ConnectorAuthType) -> Result { match auth_type { - ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + ConnectorAuthType::SignatureKey { + api_key, + key1, + api_secret, + } => Ok(Self { + password: api_secret.to_owned(), + username: api_key.to_owned(), + campaign_id: key1.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -// PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] -pub enum GigadatPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct GigadatPaymentResponse { + pub token: Secret, + pub data: GigadatPaymentData, } -impl From for common_enums::AttemptStatus { +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "camelCase")] +pub struct GigadatPaymentData { + pub transaction_id: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] +pub enum GigadatPaymentStatus { + StatusInited, + StatusSuccess, + StatusRejected, + StatusRejected1, + StatusExpired, + StatusAborted1, + StatusPending, + StatusFailed, +} + +impl From for enums::AttemptStatus { fn from(item: GigadatPaymentStatus) -> Self { match item { - GigadatPaymentStatus::Succeeded => Self::Charged, - GigadatPaymentStatus::Failed => Self::Failure, - GigadatPaymentStatus::Processing => Self::Authorizing, + GigadatPaymentStatus::StatusSuccess => Self::Charged, + GigadatPaymentStatus::StatusInited | GigadatPaymentStatus::StatusPending => { + Self::Pending + } + GigadatPaymentStatus::StatusRejected + | GigadatPaymentStatus::StatusExpired + | GigadatPaymentStatus::StatusRejected1 + | GigadatPaymentStatus::StatusAborted1 + | GigadatPaymentStatus::StatusFailed => Self::Failure, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] -pub struct GigadatPaymentsResponse { - status: GigadatPaymentStatus, - id: String, +#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] +pub struct GigadatTransactionStatusResponse { + pub status: GigadatPaymentStatus, } -impl TryFrom> +impl TryFrom> for RouterData { type Error = error_stack::Report; fn try_from( - item: ResponseRouterData, + item: ResponseRouterData, + ) -> Result { + // Will be raising a sepearte PR to populate a field connect_base_url in routerData and use it here + let base_url = CONNECTOR_BASE_URL; + + let redirect_url = format!( + "{}/webflow?transaction={}&token={}", + base_url, + item.data.connector_request_reference_id, + item.response.token.peek() + ); + + let redirection_data = Some(RedirectForm::Form { + endpoint: redirect_url, + method: Method::Get, + form_fields: Default::default(), + }); + Ok(Self { + status: enums::AttemptStatus::AuthenticationPending, + response: Ok(PaymentsResponseData::TransactionResponse { + resource_id: ResponseId::ConnectorTransactionId(item.response.data.transaction_id), + redirection_data: Box::new(redirection_data), + 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 + }) + } +} + +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), + status: enums::AttemptStatus::from(item.response.status), response: Ok(PaymentsResponseData::TransactionResponse { - resource_id: ResponseId::ConnectorTransactionId(item.response.id), + resource_id: ResponseId::NoResponseId, redirection_data: Box::new(None), mandate_reference: Box::new(None), connector_metadata: None, @@ -130,50 +258,31 @@ impl TryFrom, } impl TryFrom<&GigadatRouterData<&RefundsRouterData>> for GigadatRefundRequest { type Error = error_stack::Report; fn try_from(item: &GigadatRouterData<&RefundsRouterData>) -> Result { + let auth_type = GigadatAuthType::try_from(&item.router_data.connector_auth_type)?; Ok(Self { amount: item.amount.to_owned(), + transaction_id: item.router_data.request.connector_transaction_id.clone(), + campaign_id: auth_type.campaign_id, }) } } -// Type definition for Refund Response - -#[allow(dead_code)] -#[derive(Debug, Copy, 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)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundResponse { - id: String, - status: RefundStatus, + success: bool, + data: GigadatPaymentData, } impl TryFrom> for RefundsRouterData { @@ -181,39 +290,35 @@ impl TryFrom> for RefundsRout fn try_from( item: RefundsResponseRouterData, ) -> Result { + let refund_status = match item.http_code { + 200 => enums::RefundStatus::Success, + 400 | 401 | 422 => enums::RefundStatus::Failure, + _ => enums::RefundStatus::Pending, + }; + Ok(Self { response: Ok(RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.data.transaction_id.to_string(), + refund_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 GigadatErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, - pub network_advice_code: Option, - pub network_decline_code: Option, - pub network_error_message: Option, + pub err: String, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct GigadatRefundErrorResponse { + pub error: Vec, + pub message: String, +} + +#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +pub struct Error { + pub code: Option, + pub detail: String, } diff --git a/crates/payment_methods/src/configs/payment_connector_required_fields.rs b/crates/payment_methods/src/configs/payment_connector_required_fields.rs index eccd9b02b3..ea0abe93f8 100644 --- a/crates/payment_methods/src/configs/payment_connector_required_fields.rs +++ b/crates/payment_methods/src/configs/payment_connector_required_fields.rs @@ -2310,10 +2310,25 @@ fn get_bank_redirect_required_fields( ), ( enums::PaymentMethodType::Interac, - connectors(vec![( - Connector::Paysafe, - fields(vec![], vec![RequiredField::BillingEmail], vec![]), - )]), + connectors(vec![ + ( + Connector::Paysafe, + fields(vec![], vec![RequiredField::BillingEmail], vec![]), + ), + ( + Connector::Gigadat, + fields( + vec![], + vec![ + RequiredField::BillingEmail, + RequiredField::BillingUserFirstName, + RequiredField::BillingUserLastName, + RequiredField::BillingPhone, + ], + vec![], + ), + ), + ]), ), ]) } diff --git a/crates/router/src/core/connector_validation.rs b/crates/router/src/core/connector_validation.rs index a4bc945b93..8c9ef01017 100644 --- a/crates/router/src/core/connector_validation.rs +++ b/crates/router/src/core/connector_validation.rs @@ -263,6 +263,13 @@ impl ConnectorAuthTypeAndMetadataValidation<'_> { getnet::transformers::GetnetAuthType::try_from(self.auth_type)?; Ok(()) } + api_enums::Connector::Gigadat => { + gigadat::transformers::GigadatAuthType::try_from(self.auth_type)?; + gigadat::transformers::GigadatConnectorMetadataObject::try_from( + self.connector_meta_data, + )?; + Ok(()) + } api_enums::Connector::Globalpay => { globalpay::transformers::GlobalpayAuthType::try_from(self.auth_type)?; globalpay::transformers::GlobalPayMeta::try_from(self.connector_meta_data)?; diff --git a/crates/router/src/types/api/connector_mapping.rs b/crates/router/src/types/api/connector_mapping.rs index d0b2d5560c..61dcf05047 100644 --- a/crates/router/src/types/api/connector_mapping.rs +++ b/crates/router/src/types/api/connector_mapping.rs @@ -270,6 +270,9 @@ impl ConnectorData { enums::Connector::Getnet => { Ok(ConnectorEnum::Old(Box::new(connector::Getnet::new()))) } + enums::Connector::Gigadat => { + Ok(ConnectorEnum::Old(Box::new(connector::Gigadat::new()))) + } enums::Connector::Globalpay => { Ok(ConnectorEnum::Old(Box::new(connector::Globalpay::new()))) } diff --git a/crates/router/src/types/api/feature_matrix.rs b/crates/router/src/types/api/feature_matrix.rs index 9f514337a6..4537b42452 100644 --- a/crates/router/src/types/api/feature_matrix.rs +++ b/crates/router/src/types/api/feature_matrix.rs @@ -188,6 +188,9 @@ impl FeatureMatrixConnectorData { enums::Connector::Getnet => { Ok(ConnectorEnum::Old(Box::new(connector::Getnet::new()))) } + enums::Connector::Gigadat => { + Ok(ConnectorEnum::Old(Box::new(connector::Gigadat::new()))) + } enums::Connector::Globalpay => { Ok(ConnectorEnum::Old(Box::new(connector::Globalpay::new()))) } diff --git a/crates/router/src/types/connector_transformers.rs b/crates/router/src/types/connector_transformers.rs index ae8dfa2672..7cc41f7bc7 100644 --- a/crates/router/src/types/connector_transformers.rs +++ b/crates/router/src/types/connector_transformers.rs @@ -67,6 +67,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { api_enums::Connector::Flexiti => Self::Flexiti, api_enums::Connector::Forte => Self::Forte, api_enums::Connector::Getnet => Self::Getnet, + api_enums::Connector::Gigadat => Self::Gigadat, api_enums::Connector::Globalpay => Self::Globalpay, api_enums::Connector::Globepay => Self::Globepay, api_enums::Connector::Gocardless => Self::Gocardless, diff --git a/crates/test_utils/src/connector_auth.rs b/crates/test_utils/src/connector_auth.rs index e44d56a57d..1208173abd 100644 --- a/crates/test_utils/src/connector_auth.rs +++ b/crates/test_utils/src/connector_auth.rs @@ -59,7 +59,7 @@ pub struct ConnectorAuthentication { pub flexiti: Option, pub forte: Option, pub getnet: Option, - pub gigadat: Option, + pub gigadat: Option, pub globalpay: Option, pub globepay: Option, pub gocardless: Option, diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 139fd801c3..5818cee951 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -592,6 +592,10 @@ przelewy24 = { country = "PL", currency = "CZK,EUR,GBP,PLN" } trustly = { country = "ES,GB,SE,NO,AT,NL,DE,DK,FI,EE,LT,LV", currency = "CZK,DKK,EUR,GBP,NOK,SEK" } klarna = { country = "AU,AT,BE,CA,CZ,DK,FI,FR,DE,GR,IE,IT,NL,NZ,NO,PL,PT,ES,SE,CH,GB,US", currency = "CHF,DKK,EUR,GBP,NOK,PLN,SEK,USD,AUD,NZD,CAD" } + +[pm_filters.gigadat] +interac = { currency = "CAD"} + [pm_filters.authorizedotnet] credit = { currency = "CAD,USD" } debit = { currency = "CAD,USD" }