diff --git a/api-reference/openapi_spec.json b/api-reference/openapi_spec.json index 0014042b2335..e55485e945a7 100644 --- a/api-reference/openapi_spec.json +++ b/api-reference/openapi_spec.json @@ -6518,7 +6518,29 @@ ], "properties": { "pix": { - "type": "object" + "type": "object", + "properties": { + "pix_key": { + "type": "string", + "description": "Unique key for pix transfer", + "example": "a1f4102e-a446-4a57-bcce-6fa48899c1d1", + "nullable": true + }, + "cpf": { + "type": "integer", + "format": "int64", + "description": "CPF is a Brazilian tax identification number", + "example": "10599054689", + "nullable": true + }, + "cnpj": { + "type": "integer", + "format": "int64", + "description": "CNPJ is a Brazilian company tax identification number", + "example": "74469027417312", + "nullable": true + } + } } } }, @@ -7783,6 +7805,7 @@ "gpayments", "helcim", "iatapay", + "itaubank", "klarna", "mifinity", "mollie", @@ -19702,6 +19725,7 @@ "gocardless", "helcim", "iatapay", + "itaubank", "klarna", "mifinity", "mollie", diff --git a/config/config.example.toml b/config/config.example.toml index 443a805da0bc..8904c8ccfac0 100644 --- a/config/config.example.toml +++ b/config/config.example.toml @@ -213,7 +213,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/config/deployments/integration_test.toml b/config/deployments/integration_test.toml index fbcd7cf0364c..4fa078e72056 100644 --- a/config/deployments/integration_test.toml +++ b/config/deployments/integration_test.toml @@ -53,7 +53,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/config/deployments/production.toml b/config/deployments/production.toml index e3b84396bb3a..39481c7e346f 100644 --- a/config/deployments/production.toml +++ b/config/deployments/production.toml @@ -57,7 +57,7 @@ gocardless.base_url = "https://api.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://secure.api.itau/" klarna.base_url = "https://api{{klarna_region}}.klarna.com/" mifinity.base_url = "https://secure.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/config/deployments/sandbox.toml b/config/deployments/sandbox.toml index 2f428aa5c072..c13268bf8968 100644 --- a/config/deployments/sandbox.toml +++ b/config/deployments/sandbox.toml @@ -57,7 +57,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/config/development.toml b/config/development.toml index dd7859908d4e..fd1502084e9f 100644 --- a/config/development.toml +++ b/config/development.toml @@ -207,7 +207,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/config/docker_compose.toml b/config/docker_compose.toml index a6260055b716..ce74a674eab9 100644 --- a/config/docker_compose.toml +++ b/config/docker_compose.toml @@ -142,7 +142,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/" diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index 2fc709bf1f42..28a1296468ae 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -101,7 +101,7 @@ pub enum Connector { Gpayments, Helcim, Iatapay, - // Itaubank, template code for future usage + Itaubank, Klarna, Mifinity, Mollie, @@ -182,6 +182,7 @@ impl Connector { | (Self::Trustpay, PaymentMethod::BankRedirect) | (Self::Iatapay, _) | (Self::Volt, _) + | (Self::Itaubank, _) ) } pub fn supports_file_storage_module(&self) -> bool { @@ -227,6 +228,7 @@ impl Connector { | Self::Gpayments | Self::Helcim | Self::Iatapay + | Self::Itaubank | Self::Klarna | Self::Mifinity | Self::Mollie diff --git a/crates/api_models/src/payments.rs b/crates/api_models/src/payments.rs index 619f345a3336..33b607dd265a 100644 --- a/crates/api_models/src/payments.rs +++ b/crates/api_models/src/payments.rs @@ -1856,7 +1856,7 @@ impl GetPaymentMethodType for BankTransferData { Self::CimbVaBankTransfer { .. } => api_enums::PaymentMethodType::CimbVa, Self::DanamonVaBankTransfer { .. } => api_enums::PaymentMethodType::DanamonVa, Self::MandiriVaBankTransfer { .. } => api_enums::PaymentMethodType::MandiriVa, - Self::Pix {} => api_enums::PaymentMethodType::Pix, + Self::Pix { .. } => api_enums::PaymentMethodType::Pix, Self::Pse {} => api_enums::PaymentMethodType::Pse, Self::LocalBankTransfer { .. } => api_enums::PaymentMethodType::LocalBankTransfer, } @@ -2458,7 +2458,17 @@ pub enum BankTransferData { /// The billing details for BniVa Bank Transfer billing_details: Option, }, - Pix {}, + Pix { + /// Unique key for pix transfer + #[schema(value_type = Option, example = "a1f4102e-a446-4a57-bcce-6fa48899c1d1")] + pix_key: Option>, + /// CPF is a Brazilian tax identification number + #[schema(value_type = Option, example = "10599054689")] + cpf: Option>, + /// CNPJ is a Brazilian company tax identification number + #[schema(value_type = Option, example = "74469027417312")] + cnpj: Option>, + }, Pse {}, LocalBankTransfer { bank_code: Option, @@ -2530,7 +2540,7 @@ impl GetAddressFromPaymentMethodData for BankTransferData { email: details.email.clone(), }) } - Self::LocalBankTransfer { .. } | Self::Pix {} | Self::Pse {} => None, + Self::LocalBankTransfer { .. } | Self::Pix { .. } | Self::Pse {} => None, } } } diff --git a/crates/common_enums/src/enums.rs b/crates/common_enums/src/enums.rs index 4cc57d180a8e..16f877c4c33a 100644 --- a/crates/common_enums/src/enums.rs +++ b/crates/common_enums/src/enums.rs @@ -216,7 +216,7 @@ pub enum RoutableConnectors { Gocardless, Helcim, Iatapay, - // Itaubank, template code for future usage + Itaubank, Klarna, Mifinity, Mollie, diff --git a/crates/connector_configs/src/connector.rs b/crates/connector_configs/src/connector.rs index 6bace77e310d..d4c902488319 100644 --- a/crates/connector_configs/src/connector.rs +++ b/crates/connector_configs/src/connector.rs @@ -153,6 +153,7 @@ pub struct ConnectorConfig { #[cfg(feature = "payouts")] pub cybersource_payout: Option, pub iatapay: Option, + pub itaubank: Option, pub opennode: Option, pub bambora: Option, pub datatrans: Option, @@ -288,6 +289,7 @@ impl ConnectorConfig { Connector::Cryptopay => Ok(connector_data.cryptopay), Connector::Cybersource => Ok(connector_data.cybersource), Connector::Iatapay => Ok(connector_data.iatapay), + Connector::Itaubank => Ok(connector_data.itaubank), Connector::Opennode => Ok(connector_data.opennode), Connector::Bambora => Ok(connector_data.bambora), Connector::Datatrans => Ok(connector_data.datatrans), diff --git a/crates/connector_configs/toml/development.toml b/crates/connector_configs/toml/development.toml index c41f304fb2b3..331d9ba744d2 100644 --- a/crates/connector_configs/toml/development.toml +++ b/crates/connector_configs/toml/development.toml @@ -1611,6 +1611,13 @@ api_secret="Client Secret" [iatapay.connector_webhook_details] merchant_secret="Source verification key" +[itaubank] +[[itaubank.bank_transfer]] + payment_method_type = "pix" +[itaubank.connector_auth.BodyKey] +key1="Client Id" +api_key="Client Secret" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" diff --git a/crates/connector_configs/toml/production.toml b/crates/connector_configs/toml/production.toml index 3086f4b2b82a..cfdc2e4b6350 100644 --- a/crates/connector_configs/toml/production.toml +++ b/crates/connector_configs/toml/production.toml @@ -1348,6 +1348,13 @@ api_secret="Client Secret" [iatapay.connector_webhook_details] merchant_secret="Source verification key" +[itaubank] +[[itaubank.bank_transfer]] + payment_method_type = "pix" +[itaubank.connector_auth.BodyKey] +key1="Client Id" +api_key="Client Secret" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" diff --git a/crates/connector_configs/toml/sandbox.toml b/crates/connector_configs/toml/sandbox.toml index 806f7ab3642a..ca5f6fb72e6d 100644 --- a/crates/connector_configs/toml/sandbox.toml +++ b/crates/connector_configs/toml/sandbox.toml @@ -1609,6 +1609,13 @@ api_secret="Client Secret" [iatapay.connector_webhook_details] merchant_secret="Source verification key" +[itaubank] +[[itaubank.bank_transfer]] + payment_method_type = "pix" +[itaubank.connector_auth.BodyKey] +key1="Client Id" +api_key="Client Secret" + [klarna] [[klarna.pay_later]] payment_method_type = "klarna" diff --git a/crates/hyperswitch_domain_models/src/payment_method_data.rs b/crates/hyperswitch_domain_models/src/payment_method_data.rs index 49b6951f5f8c..0085d8af6d1e 100644 --- a/crates/hyperswitch_domain_models/src/payment_method_data.rs +++ b/crates/hyperswitch_domain_models/src/payment_method_data.rs @@ -446,9 +446,18 @@ pub enum BankTransferData { CimbVaBankTransfer {}, DanamonVaBankTransfer {}, MandiriVaBankTransfer {}, - Pix {}, + Pix { + /// Unique key for pix transfer + pix_key: Option>, + /// CPF is a Brazilian tax identification number + cpf: Option>, + /// CNPJ is a Brazilian company tax identification number + cnpj: Option>, + }, Pse {}, - LocalBankTransfer { bank_code: Option }, + LocalBankTransfer { + bank_code: Option, + }, } #[derive(Debug, Clone, Eq, PartialEq, serde::Deserialize, serde::Serialize)] @@ -914,7 +923,9 @@ impl From for BankTransferData { api_models::payments::BankTransferData::MandiriVaBankTransfer { .. } => { Self::MandiriVaBankTransfer {} } - api_models::payments::BankTransferData::Pix {} => Self::Pix {}, + api_models::payments::BankTransferData::Pix { pix_key, cpf, cnpj } => { + Self::Pix { pix_key, cpf, cnpj } + } api_models::payments::BankTransferData::Pse {} => Self::Pse {}, api_models::payments::BankTransferData::LocalBankTransfer { bank_code } => { Self::LocalBankTransfer { bank_code } diff --git a/crates/hyperswitch_domain_models/src/router_request_types.rs b/crates/hyperswitch_domain_models/src/router_request_types.rs index 825927d021ab..076e68764bce 100644 --- a/crates/hyperswitch_domain_models/src/router_request_types.rs +++ b/crates/hyperswitch_domain_models/src/router_request_types.rs @@ -408,6 +408,7 @@ pub struct PaymentsSyncData { pub payment_method_type: Option, pub currency: storage_enums::Currency, pub payment_experience: Option, + pub browser_info: Option, pub amount: MinorUnit, pub integrity_object: Option, diff --git a/crates/masking/src/serde.rs b/crates/masking/src/serde.rs index 45f7fbcb3d08..0c5782ae04d5 100644 --- a/crates/masking/src/serde.rs +++ b/crates/masking/src/serde.rs @@ -29,6 +29,7 @@ impl SerializableSecret for u8 {} impl SerializableSecret for u16 {} impl SerializableSecret for i8 {} impl SerializableSecret for i32 {} +impl SerializableSecret for i64 {} impl SerializableSecret for url::Url {} #[cfg(feature = "time")] diff --git a/crates/router/src/connector/adyen/transformers.rs b/crates/router/src/connector/adyen/transformers.rs index 693ec5fc8a65..681b994191ac 100644 --- a/crates/router/src/connector/adyen/transformers.rs +++ b/crates/router/src/connector/adyen/transformers.rs @@ -2445,7 +2445,7 @@ impl<'a> domain::BankTransferData::MandiriVaBankTransfer {} => Ok( AdyenPaymentMethod::MandiriVa(Box::new(DokuBankData::try_from(item)?)), ), - domain::BankTransferData::Pix {} => { + domain::BankTransferData::Pix { .. } => { Ok(AdyenPaymentMethod::Pix(Box::new(PmdForPaymentType { payment_type: PaymentType::Pix, }))) diff --git a/crates/router/src/connector/itaubank.rs b/crates/router/src/connector/itaubank.rs index eedb0f70fccc..aa1ccaa98c79 100644 --- a/crates/router/src/connector/itaubank.rs +++ b/crates/router/src/connector/itaubank.rs @@ -1,21 +1,21 @@ pub mod transformers; -use common_utils::types::{AmountConvertor, StringMinorUnit, StringMinorUnitForConnector}; +use common_utils::types::{AmountConvertor, StringMajorUnit, StringMajorUnitForConnector}; use error_stack::{report, ResultExt}; -use masking::ExposeInterface; +use hyperswitch_interfaces::consts; +use masking::PeekInterface; use transformers as itaubank; -use super::utils::{self as connector_utils}; +use super::utils::{ + self as connector_utils, BrowserInformationData, PaymentsAuthorizeRequestData, + PaymentsSyncRequestData, +}; use crate::{ configs::settings, core::errors::{self, CustomResult}, events::connector_api_logs::ConnectorEvent, headers, - services::{ - self, - request::{self, Mask}, - ConnectorIntegration, ConnectorValidation, - }, + services::{self, request, ConnectorIntegration, ConnectorValidation}, types::{ self, api::{self, ConnectorCommon, ConnectorCommonExt}, @@ -26,13 +26,13 @@ use crate::{ #[derive(Clone)] pub struct Itaubank { - amount_converter: &'static (dyn AmountConvertor + Sync), + amount_converter: &'static (dyn AmountConvertor + Sync), } impl Itaubank { pub fn new() -> &'static Self { &Self { - amount_converter: &StringMinorUnitForConnector, + amount_converter: &StringMajorUnitForConnector, } } } @@ -68,12 +68,18 @@ where req: &types::RouterData, _connectors: &settings::Connectors, ) -> CustomResult)>, errors::ConnectorError> { - let mut header = vec![( - headers::CONTENT_TYPE.to_string(), - self.get_content_type().to_string().into(), + let access_token = + req.access_token + .clone() + .ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "access_token", + })?; + + let header = vec![( + headers::AUTHORIZATION.to_string(), + format!("Bearer {}", access_token.token.peek()).into(), )]; - let mut api_key = self.get_auth_header(&req.connector_auth_type)?; - header.append(&mut api_key); + Ok(header) } } @@ -84,7 +90,7 @@ impl ConnectorCommon for Itaubank { } fn get_currency_unit(&self) -> api::CurrencyUnit { - api::CurrencyUnit::Minor + api::CurrencyUnit::Base } fn common_get_content_type(&self) -> &'static str { @@ -95,18 +101,6 @@ impl ConnectorCommon for Itaubank { connectors.itaubank.base_url.as_ref() } - fn get_auth_header( - &self, - auth_type: &types::ConnectorAuthType, - ) -> CustomResult)>, errors::ConnectorError> { - let auth = itaubank::ItaubankAuthType::try_from(auth_type) - .change_context(errors::ConnectorError::FailedToObtainAuthType)?; - Ok(vec![( - headers::AUTHORIZATION.to_string(), - auth.api_key.expose().into_masked(), - )]) - } - fn build_error_response( &self, res: Response, @@ -122,9 +116,12 @@ impl ConnectorCommon for Itaubank { Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: response.error.status.to_string(), + message: response + .error + .title + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: response.error.detail, attempt_status: None, connector_transaction_id: None, }) @@ -141,6 +138,111 @@ impl ConnectorIntegration for Itaubank { + fn get_url( + &self, + _req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}api/oauth/jwt", self.base_url(connectors))) + } + fn get_content_type(&self) -> &'static str { + "application/x-www-form-urlencoded" + } + fn get_headers( + &self, + _req: &types::RefreshTokenRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + let flow_header = vec![( + headers::CONTENT_TYPE.to_string(), + types::RefreshTokenType::get_content_type(self) + .to_string() + .into(), + ), + ( + headers::ACCEPT.to_string(), + "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.".to_string().into(), + ), + ( + headers::USER_AGENT.to_string(), + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36".to_string().into(), + )]; + Ok(flow_header) + } + fn get_request_body( + &self, + req: &types::RefreshTokenRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult { + let connector_req = itaubank::ItaubankAuthRequest::try_from(req)?; + + Ok(RequestContent::FormUrlEncoded(Box::new(connector_req))) + } + + fn build_request( + &self, + req: &types::RefreshTokenRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + let req = Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .headers(types::RefreshTokenType::get_headers(self, req, connectors)?) + .url(&types::RefreshTokenType::get_url(self, req, connectors)?) + .set_body(types::RefreshTokenType::get_request_body( + self, req, connectors, + )?) + .build(), + ); + + Ok(req) + } + + fn handle_response( + &self, + data: &types::RefreshTokenRouterData, + event_builder: Option<&mut ConnectorEvent>, + res: Response, + ) -> CustomResult { + let response: itaubank::ItaubankUpdateTokenResponse = res + .response + .parse_struct("ItaubankUpdateTokenResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + + fn get_error_response( + &self, + res: Response, + event_builder: Option<&mut ConnectorEvent>, + ) -> CustomResult { + let response: itaubank::ItaubankTokenErrorResponse = res + .response + .parse_struct("ItaubankTokenErrorResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + + event_builder.map(|i| i.set_error_response_body(&response)); + router_env::logger::info!(connector_response=?response); + + Ok(ErrorResponse { + status_code: res.status_code, + code: response.status.to_string(), + message: response + .title + .unwrap_or(consts::NO_ERROR_MESSAGE.to_string()), + reason: response.detail, + attempt_status: None, + connector_transaction_id: None, + }) + } } impl @@ -150,6 +252,17 @@ impl types::PaymentsResponseData, > for Itaubank { + fn build_request( + &self, + _req: &types::SetupMandateRouterData, + _connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Err(errors::ConnectorError::FlowNotSupported { + flow: "setup mandate".to_string(), + connector: "itaubank".to_string(), + } + .into()) + } } impl ConnectorIntegration @@ -160,7 +273,26 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = self.build_headers(req, connectors)?; + let browser_info = req.request.get_browser_info()?; + let mut flow_header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self) + .to_string() + .into(), + ), + ( + headers::ACCEPT.to_string(), + browser_info.get_accept_header()?.into(), + ), + ( + headers::USER_AGENT.to_string(), + browser_info.get_user_agent()?.into(), + ), + ]; + header.append(&mut flow_header); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -170,9 +302,12 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}itau-ep9-gtw-pix-recebimentos-ext-v2/v2/cob", + self.base_url(connectors) + )) } fn get_request_body( @@ -249,7 +384,26 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = self.build_headers(req, connectors)?; + let browser_info = req.request.get_browser_info()?; + let mut flow_header = vec![ + ( + headers::CONTENT_TYPE.to_string(), + types::PaymentsAuthorizeType::get_content_type(self) + .to_string() + .into(), + ), + ( + headers::ACCEPT.to_string(), + browser_info.get_accept_header()?.into(), + ), + ( + headers::USER_AGENT.to_string(), + browser_info.get_user_agent()?.into(), + ), + ]; + header.append(&mut flow_header); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -258,10 +412,17 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!( + "{}itau-ep9-gtw-pix-recebimentos-ext-v2/v2/cob/{}", + self.base_url(connectors), + req.request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)? + )) } fn build_request( @@ -285,7 +446,7 @@ impl ConnectorIntegration, res: Response, ) -> CustomResult { - let response: itaubank::ItaubankPaymentsResponse = res + let response: itaubank::ItaubankPaymentsSyncResponse = res .response .parse_struct("itaubank PaymentsSyncResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; diff --git a/crates/router/src/connector/itaubank/transformers.rs b/crates/router/src/connector/itaubank/transformers.rs index d9834c57f66c..c6cad43f23ce 100644 --- a/crates/router/src/connector/itaubank/transformers.rs +++ b/crates/router/src/connector/itaubank/transformers.rs @@ -1,20 +1,25 @@ -use common_utils::types::StringMinorUnit; +use api_models::payments; +use common_utils::{ext_traits::Encode, types::StringMajorUnit}; +use error_stack::ResultExt; use masking::Secret; use serde::{Deserialize, Serialize}; +use time::PrimitiveDateTime; +use url::Url; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{self, RouterData}, core::errors, types::{self, api, domain, storage::enums}, + utils as crate_utils, }; pub struct ItaubankRouterData { - pub amount: StringMinorUnit, + pub amount: StringMajorUnit, pub router_data: T, } -impl From<(StringMinorUnit, T)> for ItaubankRouterData { - fn from((amount, item): (StringMinorUnit, T)) -> Self { +impl From<(StringMajorUnit, T)> for ItaubankRouterData { + fn from((amount, item): (StringMajorUnit, T)) -> Self { Self { amount, router_data: item, @@ -22,19 +27,26 @@ impl From<(StringMinorUnit, T)> for ItaubankRouterData { } } -#[derive(Default, Debug, Serialize, PartialEq)] +#[derive(Default, Debug, Serialize)] pub struct ItaubankPaymentsRequest { - amount: StringMinorUnit, - card: ItaubankCard, + valor: PixPaymentValue, // amount + chave: Secret, // pix-key + devedor: ItaubankDebtor, // debtor } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct ItaubankCard { - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, +#[derive(Default, Debug, Serialize)] +pub struct PixPaymentValue { + original: StringMajorUnit, +} + +#[derive(Default, Debug, Serialize)] +pub struct ItaubankDebtor { + #[serde(skip_serializing_if = "Option::is_none")] + cpf: Option>, // CPF is a Brazilian tax identification number + #[serde(skip_serializing_if = "Option::is_none")] + cnpj: Option>, // CNPJ is a Brazilian company tax identification number + #[serde(skip_serializing_if = "Option::is_none")] + nome: Option>, // name of the debtor } impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for ItaubankPaymentsRequest { @@ -43,63 +55,190 @@ impl TryFrom<&ItaubankRouterData<&types::PaymentsAuthorizeRouterData>> for Itaub item: &ItaubankRouterData<&types::PaymentsAuthorizeRouterData>, ) -> Result { match item.router_data.request.payment_method_data.clone() { - domain::PaymentMethodData::Card(req_card) => { - let card = ItaubankCard { - 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, - }) + domain::PaymentMethodData::BankTransfer(bank_transfer_data) => { + match *bank_transfer_data { + domain::BankTransferData::Pix { pix_key, cpf, cnpj } => { + let nome = item.router_data.get_optional_billing_full_name(); + // cpf and cnpj are mutually exclusive + let devedor = match (cpf, cnpj) { + (Some(cpf), _) => ItaubankDebtor { + cpf: Some(cpf), + cnpj: None, + nome, + }, + (None, Some(cnpj)) => ItaubankDebtor { + cpf: None, + cnpj: Some(cnpj), + nome, + }, + _ => Err(errors::ConnectorError::MissingRequiredField { + field_name: "cpf and cnpj both missing in payment_method_data", + })?, + }; + Ok(Self { + valor: PixPaymentValue { + original: item.amount.to_owned(), + }, + chave: pix_key.ok_or(errors::ConnectorError::MissingRequiredField { + field_name: "pix_key", + })?, + devedor, + }) + } + domain::BankTransferData::AchBankTransfer {} + | domain::BankTransferData::SepaBankTransfer {} + | domain::BankTransferData::BacsBankTransfer {} + | domain::BankTransferData::MultibancoBankTransfer {} + | domain::BankTransferData::PermataBankTransfer {} + | domain::BankTransferData::BcaBankTransfer {} + | domain::BankTransferData::BniVaBankTransfer {} + | domain::BankTransferData::BriVaBankTransfer {} + | domain::BankTransferData::CimbVaBankTransfer {} + | domain::BankTransferData::DanamonVaBankTransfer {} + | domain::BankTransferData::MandiriVaBankTransfer {} + | domain::BankTransferData::Pse {} + | domain::BankTransferData::LocalBankTransfer { .. } => { + Err(errors::ConnectorError::NotImplemented( + "Selected payment method through itaubank".to_string(), + ) + .into()) + } + } + } + domain::PaymentMethodData::Card(_) + | domain::PaymentMethodData::CardRedirect(_) + | domain::PaymentMethodData::Wallet(_) + | domain::PaymentMethodData::PayLater(_) + | domain::PaymentMethodData::BankRedirect(_) + | domain::PaymentMethodData::BankDebit(_) + | domain::PaymentMethodData::Crypto(_) + | domain::PaymentMethodData::MandatePayment + | domain::PaymentMethodData::Reward + | domain::PaymentMethodData::RealTimePayment(_) + | domain::PaymentMethodData::Upi(_) + | domain::PaymentMethodData::Voucher(_) + | domain::PaymentMethodData::GiftCard(_) + | domain::PaymentMethodData::CardToken(_) + | domain::PaymentMethodData::OpenBanking(_) => { + Err(errors::ConnectorError::NotImplemented( + "Selected payment method through itaubank".to_string(), + ) + .into()) } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } pub struct ItaubankAuthType { - pub(super) api_key: Secret, + pub(super) client_id: Secret, + pub(super) client_secret: Secret, } impl TryFrom<&types::ConnectorAuthType> for ItaubankAuthType { type Error = error_stack::Report; fn try_from(auth_type: &types::ConnectorAuthType) -> Result { match auth_type { - types::ConnectorAuthType::HeaderKey { api_key } => Ok(Self { - api_key: api_key.to_owned(), + types::ConnectorAuthType::BodyKey { api_key, key1 } => Ok(Self { + client_secret: api_key.to_owned(), + client_id: key1.to_owned(), }), _ => Err(errors::ConnectorError::FailedToObtainAuthType.into()), } } } -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[derive(Debug, Serialize)] +pub struct ItaubankAuthRequest { + client_id: Secret, + client_secret: Secret, + grant_type: ItaubankGrantType, +} + +#[derive(Clone, Debug, Serialize)] +#[serde(rename_all = "snake_case")] +pub enum ItaubankGrantType { + ClientCredentials, +} + +impl TryFrom<&types::RefreshTokenRouterData> for ItaubankAuthRequest { + type Error = error_stack::Report; + fn try_from(item: &types::RefreshTokenRouterData) -> Result { + let auth_details = ItaubankAuthType::try_from(&item.connector_auth_type)?; + + Ok(Self { + client_id: auth_details.client_id, + client_secret: auth_details.client_secret, + grant_type: ItaubankGrantType::ClientCredentials, + }) + } +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ItaubankUpdateTokenResponse { + access_token: Secret, + expires_in: i64, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ItaubankTokenErrorResponse { + pub status: i64, + pub title: Option, + pub detail: Option, +} + +impl TryFrom> + for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData, + ) -> Result { + Ok(Self { + response: Ok(types::AccessToken { + token: item.response.access_token, + expires: item.response.expires_in, + }), + ..item.data + }) + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "SCREAMING_SNAKE_CASE")] pub enum ItaubankPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + Ativa, // Active + Concluida, // Completed + RemovidaPeloPsp, // Removed by PSP + RemovidaPeloUsuarioRecebedor, // Removed by receiving User } impl From for enums::AttemptStatus { fn from(item: ItaubankPaymentStatus) -> Self { match item { - ItaubankPaymentStatus::Succeeded => Self::Charged, - ItaubankPaymentStatus::Failed => Self::Failure, - ItaubankPaymentStatus::Processing => Self::Authorizing, + ItaubankPaymentStatus::Ativa => Self::AuthenticationPending, + ItaubankPaymentStatus::Concluida => Self::Charged, + ItaubankPaymentStatus::RemovidaPeloPsp + | ItaubankPaymentStatus::RemovidaPeloUsuarioRecebedor => Self::Failure, } } } -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct ItaubankPaymentsResponse { status: ItaubankPaymentStatus, - id: String, + calendario: ItaubankPixExpireTime, + txid: String, + #[serde(rename = "pixCopiaECola")] + pix_qr_value: String, +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ItaubankPixExpireTime { + #[serde(with = "common_utils::custom_serde::iso8601")] + criacao: PrimitiveDateTime, + expiracao: i64, } impl @@ -114,16 +253,82 @@ impl T, types::PaymentsResponseData, >, + ) -> Result { + let connector_metadata = get_qr_code_data(&item.response)?; + Ok(Self { + status: enums::AttemptStatus::from(item.response.status), + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.txid.to_owned(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata, + network_txn_id: None, + connector_response_reference_id: Some(item.response.txid), + incremental_authorization_allowed: None, + charge_id: None, + }), + ..item.data + }) + } +} + +fn get_qr_code_data( + response: &ItaubankPaymentsResponse, +) -> errors::CustomResult, errors::ConnectorError> { + let creation_time = utils::get_timestamp_in_milliseconds(&response.calendario.criacao); + // convert expiration to milliseconds and add to creation time + let expiration_time = creation_time + (response.calendario.expiracao * 1000); + + let image_data = crate_utils::QrImage::new_from_data(response.pix_qr_value.clone()) + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + + let image_data_url = Url::parse(image_data.data.clone().as_str()) + .change_context(errors::ConnectorError::ResponseHandlingFailed)?; + + let qr_code_info = payments::QrCodeInformation::QrDataUrl { + image_data_url, + display_to_timestamp: Some(expiration_time), + }; + + Some(qr_code_info.encode_to_value()) + .transpose() + .change_context(errors::ConnectorError::ResponseHandlingFailed) +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct ItaubankPaymentsSyncResponse { + status: ItaubankPaymentStatus, + txid: String, +} + +impl + TryFrom< + types::ResponseRouterData, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + ItaubankPaymentsSyncResponse, + T, + types::PaymentsResponseData, + >, ) -> Result { Ok(Self { status: enums::AttemptStatus::from(item.response.status), response: Ok(types::PaymentsResponseData::TransactionResponse { - resource_id: types::ResponseId::ConnectorTransactionId(item.response.id), + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.txid.to_owned(), + ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, - connector_response_reference_id: None, + connector_response_reference_id: Some(item.response.txid), incremental_authorization_allowed: None, charge_id: None, }), @@ -134,7 +339,7 @@ impl #[derive(Default, Debug, Serialize)] pub struct ItaubankRefundRequest { - pub amount: StringMinorUnit, + pub amount: StringMajorUnit, } impl TryFrom<&ItaubankRouterData<&types::RefundsRouterData>> for ItaubankRefundRequest { @@ -149,11 +354,10 @@ impl TryFrom<&ItaubankRouterData<&types::RefundsRouterData>> for ItaubankR } #[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] +#[derive(Debug, Serialize, Deserialize, Clone)] pub enum RefundStatus { Succeeded, Failed, - #[default] Processing, } @@ -167,7 +371,7 @@ impl From for enums::RefundStatus { } } -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Clone, Serialize, Deserialize)] pub struct RefundResponse { id: String, status: RefundStatus, @@ -207,10 +411,14 @@ impl TryFrom> } } -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Serialize, Deserialize)] pub struct ItaubankErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, + pub error: ItaubankErrorBody, +} + +#[derive(Debug, Serialize, Deserialize)] +pub struct ItaubankErrorBody { + pub status: u16, + pub title: Option, + pub detail: Option, } diff --git a/crates/router/src/connector/paypal/transformers.rs b/crates/router/src/connector/paypal/transformers.rs index 9718fd5a8ded..fb70581cd972 100644 --- a/crates/router/src/connector/paypal/transformers.rs +++ b/crates/router/src/connector/paypal/transformers.rs @@ -632,7 +632,7 @@ impl TryFrom<&domain::BankTransferData> for PaypalPaymentsRequest { | domain::BankTransferData::CimbVaBankTransfer { .. } | domain::BankTransferData::DanamonVaBankTransfer { .. } | domain::BankTransferData::MandiriVaBankTransfer { .. } - | domain::BankTransferData::Pix {} + | domain::BankTransferData::Pix { .. } | domain::BankTransferData::Pse {} | domain::BankTransferData::LocalBankTransfer { .. } => { Err(errors::ConnectorError::NotImplemented( diff --git a/crates/router/src/connector/stripe/transformers.rs b/crates/router/src/connector/stripe/transformers.rs index 2524a1aeacff..786f4a060b7e 100644 --- a/crates/router/src/connector/stripe/transformers.rs +++ b/crates/router/src/connector/stripe/transformers.rs @@ -1255,10 +1255,12 @@ fn create_stripe_payment_method( Some(StripePaymentMethodType::CustomerBalance), billing_address, )), - domain::BankTransferData::Pix {} => Err(errors::ConnectorError::NotImplemented( - connector_util::get_unimplemented_payment_method_error_message("stripe"), - ) - .into()), + domain::BankTransferData::Pix { .. } => { + Err(errors::ConnectorError::NotImplemented( + connector_util::get_unimplemented_payment_method_error_message("stripe"), + ) + .into()) + } domain::BankTransferData::Pse {} | domain::BankTransferData::LocalBankTransfer { .. } | domain::BankTransferData::PermataBankTransfer { .. } @@ -3714,7 +3716,7 @@ impl payment_method_type: StripePaymentMethodType::CustomerBalance, })), )), - domain::BankTransferData::Pix {} + domain::BankTransferData::Pix { .. } | domain::BankTransferData::Pse {} | domain::BankTransferData::PermataBankTransfer { .. } | domain::BankTransferData::BcaBankTransfer { .. } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index cdcc8411d917..8f732e143126 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -1064,6 +1064,7 @@ impl PaymentsCompleteAuthorizeRequestData for types::CompleteAuthorizeData { pub trait PaymentsSyncRequestData { fn is_auto_capture(&self) -> Result; fn get_connector_transaction_id(&self) -> CustomResult; + fn get_browser_info(&self) -> Result; } impl PaymentsSyncRequestData for types::PaymentsSyncData { @@ -1084,6 +1085,11 @@ impl PaymentsSyncRequestData for types::PaymentsSyncData { .change_context(errors::ConnectorError::MissingConnectorTransactionID)?, } } + fn get_browser_info(&self) -> Result { + self.browser_info + .clone() + .ok_or_else(missing_field_err("browser_info")) + } } #[cfg(feature = "payouts")] @@ -2811,7 +2817,7 @@ impl From for PaymentMethodDataType { domain::payments::BankTransferData::MandiriVaBankTransfer { .. } => { Self::MandiriVaBankTransfer } - domain::payments::BankTransferData::Pix {} => Self::Pix, + domain::payments::BankTransferData::Pix { .. } => Self::Pix, domain::payments::BankTransferData::Pse {} => Self::Pse, domain::payments::BankTransferData::LocalBankTransfer { .. } => { Self::LocalBankTransfer diff --git a/crates/router/src/connector/zsl/transformers.rs b/crates/router/src/connector/zsl/transformers.rs index ada1de0e6188..19a79ab48a35 100644 --- a/crates/router/src/connector/zsl/transformers.rs +++ b/crates/router/src/connector/zsl/transformers.rs @@ -158,7 +158,7 @@ impl TryFrom<&ZslRouterData<&types::PaymentsAuthorizeRouterData>> for ZslPayment | domain::BankTransferData::CimbVaBankTransfer { .. } | domain::BankTransferData::DanamonVaBankTransfer { .. } | domain::BankTransferData::MandiriVaBankTransfer { .. } - | domain::BankTransferData::Pix {} + | domain::BankTransferData::Pix { .. } | domain::BankTransferData::Pse {} => { Err(errors::ConnectorError::NotImplemented( connector_utils::get_unimplemented_payment_method_error_message( diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 8a7854d658bf..399ee74e2cec 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -2371,6 +2371,10 @@ pub(crate) fn validate_auth_and_metadata_type_with_connector( iatapay::transformers::IatapayAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Itaubank => { + itaubank::transformers::ItaubankAuthType::try_from(val)?; + Ok(()) + } api_enums::Connector::Klarna => { klarna::transformers::KlarnaAuthType::try_from(val)?; klarna::transformers::KlarnaConnectorMetadataObject::try_from(connector_meta_data)?; diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index 7da5ec132ac7..9b0fee274eef 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -1407,10 +1407,20 @@ impl TryFrom> for types::PaymentsAuthoriz } impl TryFrom> for types::PaymentsSyncData { - type Error = errors::ApiErrorResponse; + type Error = error_stack::Report; fn try_from(additional_data: PaymentAdditionalData<'_, F>) -> Result { let payment_data = additional_data.payment_data; + let browser_info: Option = payment_data + .payment_attempt + .browser_info + .clone() + .map(|b| b.parse_value("BrowserInformation")) + .transpose() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + })?; + let amount = payment_data .surcharge_details .as_ref() @@ -1438,6 +1448,7 @@ impl TryFrom> for types::PaymentsSyncData payment_method_type: payment_data.payment_attempt.payment_method_type, currency: payment_data.currency, payment_experience: payment_data.payment_attempt.payment_experience, + browser_info, }) } } diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 38000f7b6648..e316edf8ef1b 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -47,6 +47,7 @@ static ALLOC: mimalloc::MiMalloc = mimalloc::MiMalloc; /// Header Constants pub mod headers { pub const ACCEPT: &str = "Accept"; + pub const ACCEPT_LANGUAGE: &str = "Accept-Language"; pub const KEY: &str = "key"; pub const API_KEY: &str = "API-KEY"; pub const APIKEY: &str = "apikey"; @@ -59,6 +60,7 @@ pub mod headers { pub const NONCE: &str = "nonce"; pub const TIMESTAMP: &str = "Timestamp"; pub const TOKEN: &str = "token"; + pub const USER_AGENT: &str = "User-Agent"; pub const X_API_KEY: &str = "X-API-KEY"; pub const X_API_VERSION: &str = "X-ApiVersion"; pub const X_FORWARDED_FOR: &str = "X-Forwarded-For"; diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 92da4ff1960f..2487cf1db61a 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -438,6 +438,9 @@ impl ConnectorData { enums::Connector::Iatapay => { Ok(ConnectorEnum::Old(Box::new(connector::Iatapay::new()))) } + enums::Connector::Itaubank => { + Ok(ConnectorEnum::Old(Box::new(connector::Itaubank::new()))) + } enums::Connector::Klarna => Ok(ConnectorEnum::Old(Box::new(&connector::Klarna))), enums::Connector::Mollie => Ok(ConnectorEnum::Old(Box::new(&connector::Mollie))), enums::Connector::Nmi => Ok(ConnectorEnum::Old(Box::new(connector::Nmi::new()))), diff --git a/crates/router/src/types/transformers.rs b/crates/router/src/types/transformers.rs index d32f62ed0be0..76756bdd50af 100644 --- a/crates/router/src/types/transformers.rs +++ b/crates/router/src/types/transformers.rs @@ -255,6 +255,7 @@ impl ForeignTryFrom for common_enums::RoutableConnectors { } api_enums::Connector::Helcim => Self::Helcim, api_enums::Connector::Iatapay => Self::Iatapay, + api_enums::Connector::Itaubank => Self::Itaubank, api_enums::Connector::Klarna => Self::Klarna, api_enums::Connector::Mifinity => Self::Mifinity, api_enums::Connector::Mollie => Self::Mollie, diff --git a/crates/router/tests/connectors/bambora.rs b/crates/router/tests/connectors/bambora.rs index a4cb9b4c5e05..a5926d216023 100644 --- a/crates/router/tests/connectors/bambora.rs +++ b/crates/router/tests/connectors/bambora.rs @@ -114,6 +114,7 @@ async fn should_sync_authorized_payment() { payment_experience: None, integrity_object: None, amount: MinorUnit::new(100), + browser_info: None, }), None, ) @@ -233,6 +234,7 @@ async fn should_sync_auto_captured_payment() { payment_experience: None, integrity_object: None, amount: MinorUnit::new(100), + browser_info: None, }), None, ) diff --git a/crates/router/tests/connectors/forte.rs b/crates/router/tests/connectors/forte.rs index 45a30614bfa8..2a9926df654d 100644 --- a/crates/router/tests/connectors/forte.rs +++ b/crates/router/tests/connectors/forte.rs @@ -163,6 +163,7 @@ async fn should_sync_authorized_payment() { payment_experience: None, integrity_object: None, amount: MinorUnit::new(100), + browser_info: None, }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/itaubank.rs b/crates/router/tests/connectors/itaubank.rs index 4d47f566c009..221bf7a449de 100644 --- a/crates/router/tests/connectors/itaubank.rs +++ b/crates/router/tests/connectors/itaubank.rs @@ -9,10 +9,10 @@ struct ItaubankTest; impl ConnectorActions for ItaubankTest {} impl utils::Connector for ItaubankTest { fn get_data(&self) -> api::ConnectorData { - use router::connector::Adyen; + use router::connector::Itaubank; utils::construct_connector_data_old( - Box::new(Adyen::new()), - types::Connector::Adyen, + Box::new(Itaubank::new()), + types::Connector::Itaubank, api::GetToken::Connector, None, ) diff --git a/crates/router/tests/connectors/nexinets.rs b/crates/router/tests/connectors/nexinets.rs index 7909aa9c7fca..8623ae48e008 100644 --- a/crates/router/tests/connectors/nexinets.rs +++ b/crates/router/tests/connectors/nexinets.rs @@ -130,6 +130,7 @@ async fn should_sync_authorized_payment() { payment_experience: None, integrity_object: None, amount: MinorUnit::new(100), + browser_info: None, }), None, ) diff --git a/crates/router/tests/connectors/paypal.rs b/crates/router/tests/connectors/paypal.rs index 8de891ef09d9..d3891729d94b 100644 --- a/crates/router/tests/connectors/paypal.rs +++ b/crates/router/tests/connectors/paypal.rs @@ -147,6 +147,7 @@ async fn should_sync_authorized_payment() { payment_experience: None, integrity_object: None, amount: MinorUnit::new(100), + browser_info: None, }), get_default_payment_info(), ) @@ -348,6 +349,7 @@ async fn should_sync_auto_captured_payment() { payment_experience: None, amount: MinorUnit::new(100), integrity_object: None, + browser_info: None, }), get_default_payment_info(), ) diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 736c9ae6b536..c3d9b6e0c3f2 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -1001,6 +1001,7 @@ impl Default for PaymentSyncType { payment_experience: None, amount: MinorUnit::new(100), integrity_object: None, + browser_info: None, }; Self(data) } diff --git a/crates/router/tests/connectors/zen.rs b/crates/router/tests/connectors/zen.rs index 2b9ea8f8211c..6e52db340971 100644 --- a/crates/router/tests/connectors/zen.rs +++ b/crates/router/tests/connectors/zen.rs @@ -108,6 +108,7 @@ async fn should_sync_authorized_payment() { payment_experience: None, amount: MinorUnit::new(100), integrity_object: None, + browser_info: None, }), None, ) @@ -227,6 +228,7 @@ async fn should_sync_auto_captured_payment() { payment_experience: None, amount: MinorUnit::new(100), integrity_object: None, + browser_info: None, }), None, ) diff --git a/cypress-tests/cypress/e2e/PaymentUtils/ItauBank.js b/cypress-tests/cypress/e2e/PaymentUtils/ItauBank.js new file mode 100644 index 000000000000..ab66b3f743ea --- /dev/null +++ b/cypress-tests/cypress/e2e/PaymentUtils/ItauBank.js @@ -0,0 +1,68 @@ +export const connectorDetails = { + card_pm: { + ZeroAuthMandate: { + Response: { + status: 500, + body: { + error: { + type: "invalid_request", + message: "setup mandate flow not supported", + code: "IR_20", + }, + }, + }, + }, + }, + bank_transfer_pm: { + PaymentIntent: { + Request: { + currency: "BRL", + }, + Response: { + status: 200, + body: { + status: "requires_payment_method", + }, + }, + }, + Pix: { + Request: { + payment_method: "bank_transfer", + payment_method_type: "pix", + payment_method_data: { + bank_transfer: { + pix: { + pix_key: "a1f4102e-a446-4a57-bcce-6fa48899c1d1", + cnpj: 74469027417312, + cpf: 10599054689 + }, + }, + }, + billing: { + address: { + line1: "1467", + line2: "Harrison Street", + line3: "Harrison Street", + city: "San Fransico", + state: "California", + zip: "94122", + country: "BR", + first_name: "joseph", + last_name: "Doe", + }, + phone: { + number: "9123456789", + country_code: "+91", + }, + }, + currency: "BRL", + }, + Response: { + status: 200, + body: { + status: "requires_customer_action", + }, + }, + }, + }, +}; diff --git a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js index a2f5d22fe1c1..a75b95024dcc 100644 --- a/cypress-tests/cypress/e2e/PaymentUtils/Utils.js +++ b/cypress-tests/cypress/e2e/PaymentUtils/Utils.js @@ -7,6 +7,7 @@ import { } from "./Commons.js"; import { connectorDetails as cybersourceConnectorDetails } from "./Cybersource.js"; import { connectorDetails as iatapayConnectorDetails } from "./Iatapay.js"; +import { connectorDetails as itaubankConnectorDetails } from "./ItauBank.js"; import { connectorDetails as nmiConnectorDetails } from "./Nmi.js"; import { connectorDetails as paypalConnectorDetails } from "./Paypal.js"; import { connectorDetails as stripeConnectorDetails } from "./Stripe.js"; @@ -20,6 +21,7 @@ const connectorDetails = { commons: CommonConnectorDetails, cybersource: cybersourceConnectorDetails, iatapay: iatapayConnectorDetails, + itaubank: itaubankConnectorDetails, nmi: nmiConnectorDetails, paypal: paypalConnectorDetails, stripe: stripeConnectorDetails, diff --git a/cypress-tests/cypress/support/commands.js b/cypress-tests/cypress/support/commands.js index 5aa98a6e4a84..5901ba339ea7 100644 --- a/cypress-tests/cypress/support/commands.js +++ b/cypress-tests/cypress/support/commands.js @@ -666,10 +666,26 @@ Cypress.Commands.add( expect(response.body) .to.have.property("next_action") .to.have.property("qr_code_url"); - globalState.set( - "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, - response.body.next_action.qr_code_url - ); + if (response.body.next_action.qr_code_url !== null) { + globalState.set( + "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, + response.body.next_action.qr_code_url + ); + globalState.set( + "nextActionType", + "qr_code_url" + ); + } + else{ + globalState.set( + "nextActionUrl", // This is intentionally kept as nextActionUrl to avoid issues during handleRedirection call, + response.body.next_action.image_data_url + ); + globalState.set( + "nextActionType", + "image_data_url" + ); + } break; default: expect(response.body) @@ -1327,12 +1343,14 @@ Cypress.Commands.add( let connectorId = globalState.get("connectorId"); let expected_url = new URL(expected_redirection); let redirection_url = new URL(globalState.get("nextActionUrl")); + let next_action_type = globalState.get("nextActionType") cy.log(payment_method_type); handleRedirection( "bank_transfer", { redirection_url, expected_url }, connectorId, - payment_method_type + payment_method_type, + { next_action_type } ); } ); diff --git a/cypress-tests/cypress/support/redirectionHandler.js b/cypress-tests/cypress/support/redirectionHandler.js index c493862e401d..278d20b27587 100644 --- a/cypress-tests/cypress/support/redirectionHandler.js +++ b/cypress-tests/cypress/support/redirectionHandler.js @@ -9,7 +9,8 @@ export function handleRedirection( redirection_type, urls, connectorId, - payment_method_type + payment_method_type, + handler_metadata ) { switch (redirection_type) { case "bank_redirect": @@ -25,7 +26,8 @@ export function handleRedirection( urls.redirection_url, urls.expected_url, connectorId, - payment_method_type + payment_method_type, + handler_metadata.next_action_type ); break; case "three_ds": @@ -48,27 +50,53 @@ function bankTransferRedirection( redirection_url, expected_url, connectorId, - payment_method_type + payment_method_type, + next_action_type ) { - cy.request(redirection_url.href).then((response) => { - switch (connectorId) { - case "adyen": - switch (payment_method_type) { - case "pix": - expect(response.status).to.eq(200); - fetchAndParseQRCode(redirection_url.href).then((qrCodeData) => { - expect(qrCodeData).to.eq("TestQRCodeEMVToken"); - }); + switch (next_action_type) { + case "qr_code_url": + cy.request(redirection_url.href).then((response) => { + switch (connectorId) { + case "adyen": + switch (payment_method_type) { + case "pix": + expect(response.status).to.eq(200); + fetchAndParseQRCode(redirection_url.href).then((qrCodeData) => { + expect(qrCodeData).to.eq("TestQRCodeEMVToken"); + }); + break; + default: + verifyReturnUrl(redirection_url, expected_url, true); + // expected_redirection can be used here to handle other payment methods + } break; default: verifyReturnUrl(redirection_url, expected_url, true); - // expected_redirection can be used here to handle other payment methods } - break; - default: - verifyReturnUrl(redirection_url, expected_url, true); - } - }); + }); + break; + case "image_data_url": + switch (connectorId) { + case "itaubank": + switch (payment_method_type) { + case "pix": + fetchAndParseImageData(redirection_url).then((qrCodeData) => { + console.log("Imaage_data>>>>>>>>>", qrCodeData) + expect(qrCodeData).to.contains('itau.com.br/pix/qr/v2') // image data contains the following value + }); + break; + default: + verifyReturnUrl(redirection_url, expected_url, true); + } + break; + default: + verifyReturnUrl(redirection_url, expected_url, true); + } + break; + default: + verifyReturnUrl(redirection_url, expected_url, true); + } + } function bankRedirectRedirection( @@ -416,3 +444,32 @@ async function fetchAndParseQRCode(url) { reader.readAsDataURL(blob); }); } + +async function fetchAndParseImageData(url) { + return await new Promise((resolve, reject) => { + const image = new Image(); + image.src = url; + + image.onload = () => { + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + canvas.width = image.width; + canvas.height = image.height; + ctx.drawImage(image, 0, 0); + + const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); + const qrCodeData = jsQR( + imageData.data, + imageData.width, + imageData.height + ); + + if (qrCodeData) { + resolve(qrCodeData.data); + } else { + reject(new Error("Failed to decode QR code")); + } + }; + image.onerror = reject; // Handle image loading errors + }); +} diff --git a/loadtest/config/development.toml b/loadtest/config/development.toml index 2daeecf2854a..39cef77becb6 100644 --- a/loadtest/config/development.toml +++ b/loadtest/config/development.toml @@ -107,7 +107,7 @@ gocardless.base_url = "https://api-sandbox.gocardless.com" gpayments.base_url = "https://{{merchant_endpoint_prefix}}-test.api.as1.gpayments.net" helcim.base_url = "https://api.helcim.com/" iatapay.base_url = "https://sandbox.iata-pay.iata.org/api/v1" -itaubank.base_url = "https://sandbox.devportal.itau.com.br/itau-ep9-gtw-pix-recebimentos-ext-v2/v2" +itaubank.base_url = "https://sandbox.devportal.itau.com.br/" klarna.base_url = "https://api{{klarna_region}}.playground.klarna.com/" mifinity.base_url = "https://demo.mifinity.com/" mollie.base_url = "https://api.mollie.com/v2/"