diff --git a/connector-template/mod.rs b/connector-template/mod.rs index 6c311844c8..9f9793d7d7 100644 --- a/connector-template/mod.rs +++ b/connector-template/mod.rs @@ -57,7 +57,7 @@ where ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self).to_string().into(), + self.get_content_type().to_string().into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; header.append(&mut api_key); diff --git a/crates/api_models/src/enums.rs b/crates/api_models/src/enums.rs index fd6b92a91c..7a02d3dee6 100644 --- a/crates/api_models/src/enums.rs +++ b/crates/api_models/src/enums.rs @@ -91,7 +91,7 @@ pub enum Connector { Globalpay, Globepay, Gocardless, - //Helcim, added as template code for future usage, + Helcim, Iatapay, Klarna, Mollie, @@ -209,7 +209,7 @@ pub enum RoutableConnectors { Globalpay, Globepay, Gocardless, - //Helcim, added as template code for future usage, + Helcim, Iatapay, Klarna, Mollie, diff --git a/crates/common_enums/src/transformers.rs b/crates/common_enums/src/transformers.rs index 8f967ddf94..73f736cdee 100644 --- a/crates/common_enums/src/transformers.rs +++ b/crates/common_enums/src/transformers.rs @@ -10,6 +10,261 @@ impl Display for NumericCountryCodeParseError { } } +impl CountryAlpha2 { + pub const fn from_alpha2_to_alpha3(code: Self) -> CountryAlpha3 { + match code { + Self::AF => CountryAlpha3::AFG, + Self::AX => CountryAlpha3::ALA, + Self::AL => CountryAlpha3::ALB, + Self::DZ => CountryAlpha3::DZA, + Self::AS => CountryAlpha3::ASM, + Self::AD => CountryAlpha3::AND, + Self::AO => CountryAlpha3::AGO, + Self::AI => CountryAlpha3::AIA, + Self::AQ => CountryAlpha3::ATA, + Self::AG => CountryAlpha3::ATG, + Self::AR => CountryAlpha3::ARG, + Self::AM => CountryAlpha3::ARM, + Self::AW => CountryAlpha3::ABW, + Self::AU => CountryAlpha3::AUS, + Self::AT => CountryAlpha3::AUT, + Self::AZ => CountryAlpha3::AZE, + Self::BS => CountryAlpha3::BHS, + Self::BH => CountryAlpha3::BHR, + Self::BD => CountryAlpha3::BGD, + Self::BB => CountryAlpha3::BRB, + Self::BY => CountryAlpha3::BLR, + Self::BE => CountryAlpha3::BEL, + Self::BZ => CountryAlpha3::BLZ, + Self::BJ => CountryAlpha3::BEN, + Self::BM => CountryAlpha3::BMU, + Self::BT => CountryAlpha3::BTN, + Self::BO => CountryAlpha3::BOL, + Self::BQ => CountryAlpha3::BES, + Self::BA => CountryAlpha3::BIH, + Self::BW => CountryAlpha3::BWA, + Self::BV => CountryAlpha3::BVT, + Self::BR => CountryAlpha3::BRA, + Self::IO => CountryAlpha3::IOT, + Self::BN => CountryAlpha3::BRN, + Self::BG => CountryAlpha3::BGR, + Self::BF => CountryAlpha3::BFA, + Self::BI => CountryAlpha3::BDI, + Self::CV => CountryAlpha3::CPV, + Self::KH => CountryAlpha3::KHM, + Self::CM => CountryAlpha3::CMR, + Self::CA => CountryAlpha3::CAN, + Self::KY => CountryAlpha3::CYM, + Self::CF => CountryAlpha3::CAF, + Self::TD => CountryAlpha3::TCD, + Self::CL => CountryAlpha3::CHL, + Self::CN => CountryAlpha3::CHN, + Self::CX => CountryAlpha3::CXR, + Self::CC => CountryAlpha3::CCK, + Self::CO => CountryAlpha3::COL, + Self::KM => CountryAlpha3::COM, + Self::CG => CountryAlpha3::COG, + Self::CD => CountryAlpha3::COD, + Self::CK => CountryAlpha3::COK, + Self::CR => CountryAlpha3::CRI, + Self::CI => CountryAlpha3::CIV, + Self::HR => CountryAlpha3::HRV, + Self::CU => CountryAlpha3::CUB, + Self::CW => CountryAlpha3::CUW, + Self::CY => CountryAlpha3::CYP, + Self::CZ => CountryAlpha3::CZE, + Self::DK => CountryAlpha3::DNK, + Self::DJ => CountryAlpha3::DJI, + Self::DM => CountryAlpha3::DMA, + Self::DO => CountryAlpha3::DOM, + Self::EC => CountryAlpha3::ECU, + Self::EG => CountryAlpha3::EGY, + Self::SV => CountryAlpha3::SLV, + Self::GQ => CountryAlpha3::GNQ, + Self::ER => CountryAlpha3::ERI, + Self::EE => CountryAlpha3::EST, + Self::ET => CountryAlpha3::ETH, + Self::FK => CountryAlpha3::FLK, + Self::FO => CountryAlpha3::FRO, + Self::FJ => CountryAlpha3::FJI, + Self::FI => CountryAlpha3::FIN, + Self::FR => CountryAlpha3::FRA, + Self::GF => CountryAlpha3::GUF, + Self::PF => CountryAlpha3::PYF, + Self::TF => CountryAlpha3::ATF, + Self::GA => CountryAlpha3::GAB, + Self::GM => CountryAlpha3::GMB, + Self::GE => CountryAlpha3::GEO, + Self::DE => CountryAlpha3::DEU, + Self::GH => CountryAlpha3::GHA, + Self::GI => CountryAlpha3::GIB, + Self::GR => CountryAlpha3::GRC, + Self::GL => CountryAlpha3::GRL, + Self::GD => CountryAlpha3::GRD, + Self::GP => CountryAlpha3::GLP, + Self::GU => CountryAlpha3::GUM, + Self::GT => CountryAlpha3::GTM, + Self::GG => CountryAlpha3::GGY, + Self::GN => CountryAlpha3::GIN, + Self::GW => CountryAlpha3::GNB, + Self::GY => CountryAlpha3::GUY, + Self::HT => CountryAlpha3::HTI, + Self::HM => CountryAlpha3::HMD, + Self::VA => CountryAlpha3::VAT, + Self::HN => CountryAlpha3::HND, + Self::HK => CountryAlpha3::HKG, + Self::HU => CountryAlpha3::HUN, + Self::IS => CountryAlpha3::ISL, + Self::IN => CountryAlpha3::IND, + Self::ID => CountryAlpha3::IDN, + Self::IR => CountryAlpha3::IRN, + Self::IQ => CountryAlpha3::IRQ, + Self::IE => CountryAlpha3::IRL, + Self::IM => CountryAlpha3::IMN, + Self::IL => CountryAlpha3::ISR, + Self::IT => CountryAlpha3::ITA, + Self::JM => CountryAlpha3::JAM, + Self::JP => CountryAlpha3::JPN, + Self::JE => CountryAlpha3::JEY, + Self::JO => CountryAlpha3::JOR, + Self::KZ => CountryAlpha3::KAZ, + Self::KE => CountryAlpha3::KEN, + Self::KI => CountryAlpha3::KIR, + Self::KP => CountryAlpha3::PRK, + Self::KR => CountryAlpha3::KOR, + Self::KW => CountryAlpha3::KWT, + Self::KG => CountryAlpha3::KGZ, + Self::LA => CountryAlpha3::LAO, + Self::LV => CountryAlpha3::LVA, + Self::LB => CountryAlpha3::LBN, + Self::LS => CountryAlpha3::LSO, + Self::LR => CountryAlpha3::LBR, + Self::LY => CountryAlpha3::LBY, + Self::LI => CountryAlpha3::LIE, + Self::LT => CountryAlpha3::LTU, + Self::LU => CountryAlpha3::LUX, + Self::MO => CountryAlpha3::MAC, + Self::MK => CountryAlpha3::MKD, + Self::MG => CountryAlpha3::MDG, + Self::MW => CountryAlpha3::MWI, + Self::MY => CountryAlpha3::MYS, + Self::MV => CountryAlpha3::MDV, + Self::ML => CountryAlpha3::MLI, + Self::MT => CountryAlpha3::MLT, + Self::MH => CountryAlpha3::MHL, + Self::MQ => CountryAlpha3::MTQ, + Self::MR => CountryAlpha3::MRT, + Self::MU => CountryAlpha3::MUS, + Self::YT => CountryAlpha3::MYT, + Self::MX => CountryAlpha3::MEX, + Self::FM => CountryAlpha3::FSM, + Self::MD => CountryAlpha3::MDA, + Self::MC => CountryAlpha3::MCO, + Self::MN => CountryAlpha3::MNG, + Self::ME => CountryAlpha3::MNE, + Self::MS => CountryAlpha3::MSR, + Self::MA => CountryAlpha3::MAR, + Self::MZ => CountryAlpha3::MOZ, + Self::MM => CountryAlpha3::MMR, + Self::NA => CountryAlpha3::NAM, + Self::NR => CountryAlpha3::NRU, + Self::NP => CountryAlpha3::NPL, + Self::NL => CountryAlpha3::NLD, + Self::NC => CountryAlpha3::NCL, + Self::NZ => CountryAlpha3::NZL, + Self::NI => CountryAlpha3::NIC, + Self::NE => CountryAlpha3::NER, + Self::NG => CountryAlpha3::NGA, + Self::NU => CountryAlpha3::NIU, + Self::NF => CountryAlpha3::NFK, + Self::MP => CountryAlpha3::MNP, + Self::NO => CountryAlpha3::NOR, + Self::OM => CountryAlpha3::OMN, + Self::PK => CountryAlpha3::PAK, + Self::PW => CountryAlpha3::PLW, + Self::PS => CountryAlpha3::PSE, + Self::PA => CountryAlpha3::PAN, + Self::PG => CountryAlpha3::PNG, + Self::PY => CountryAlpha3::PRY, + Self::PE => CountryAlpha3::PER, + Self::PH => CountryAlpha3::PHL, + Self::PN => CountryAlpha3::PCN, + Self::PL => CountryAlpha3::POL, + Self::PT => CountryAlpha3::PRT, + Self::PR => CountryAlpha3::PRI, + Self::QA => CountryAlpha3::QAT, + Self::RE => CountryAlpha3::REU, + Self::RO => CountryAlpha3::ROU, + Self::RU => CountryAlpha3::RUS, + Self::RW => CountryAlpha3::RWA, + Self::BL => CountryAlpha3::BLM, + Self::SH => CountryAlpha3::SHN, + Self::KN => CountryAlpha3::KNA, + Self::LC => CountryAlpha3::LCA, + Self::MF => CountryAlpha3::MAF, + Self::PM => CountryAlpha3::SPM, + Self::VC => CountryAlpha3::VCT, + Self::WS => CountryAlpha3::WSM, + Self::SM => CountryAlpha3::SMR, + Self::ST => CountryAlpha3::STP, + Self::SA => CountryAlpha3::SAU, + Self::SN => CountryAlpha3::SEN, + Self::RS => CountryAlpha3::SRB, + Self::SC => CountryAlpha3::SYC, + Self::SL => CountryAlpha3::SLE, + Self::SG => CountryAlpha3::SGP, + Self::SX => CountryAlpha3::SXM, + Self::SK => CountryAlpha3::SVK, + Self::SI => CountryAlpha3::SVN, + Self::SB => CountryAlpha3::SLB, + Self::SO => CountryAlpha3::SOM, + Self::ZA => CountryAlpha3::ZAF, + Self::GS => CountryAlpha3::SGS, + Self::SS => CountryAlpha3::SSD, + Self::ES => CountryAlpha3::ESP, + Self::LK => CountryAlpha3::LKA, + Self::SD => CountryAlpha3::SDN, + Self::SR => CountryAlpha3::SUR, + Self::SJ => CountryAlpha3::SJM, + Self::SZ => CountryAlpha3::SWZ, + Self::SE => CountryAlpha3::SWE, + Self::CH => CountryAlpha3::CHE, + Self::SY => CountryAlpha3::SYR, + Self::TW => CountryAlpha3::TWN, + Self::TJ => CountryAlpha3::TJK, + Self::TZ => CountryAlpha3::TZA, + Self::TH => CountryAlpha3::THA, + Self::TL => CountryAlpha3::TLS, + Self::TG => CountryAlpha3::TGO, + Self::TK => CountryAlpha3::TKL, + Self::TO => CountryAlpha3::TON, + Self::TT => CountryAlpha3::TTO, + Self::TN => CountryAlpha3::TUN, + Self::TR => CountryAlpha3::TUR, + Self::TM => CountryAlpha3::TKM, + Self::TC => CountryAlpha3::TCA, + Self::TV => CountryAlpha3::TUV, + Self::UG => CountryAlpha3::UGA, + Self::UA => CountryAlpha3::UKR, + Self::AE => CountryAlpha3::ARE, + Self::GB => CountryAlpha3::GBR, + Self::US => CountryAlpha3::USA, + Self::UM => CountryAlpha3::UMI, + Self::UY => CountryAlpha3::URY, + Self::UZ => CountryAlpha3::UZB, + Self::VU => CountryAlpha3::VUT, + Self::VE => CountryAlpha3::VEN, + Self::VN => CountryAlpha3::VNM, + Self::VG => CountryAlpha3::VGB, + Self::VI => CountryAlpha3::VIR, + Self::WF => CountryAlpha3::WLF, + Self::EH => CountryAlpha3::ESH, + Self::YE => CountryAlpha3::YEM, + Self::ZM => CountryAlpha3::ZMB, + Self::ZW => CountryAlpha3::ZWE, + } + } +} impl Country { pub const fn from_alpha2(code: CountryAlpha2) -> Self { match code { diff --git a/crates/router/src/connector/gocardless/transformers.rs b/crates/router/src/connector/gocardless/transformers.rs index c5322ccf7f..d3b2d24476 100644 --- a/crates/router/src/connector/gocardless/transformers.rs +++ b/crates/router/src/connector/gocardless/transformers.rs @@ -9,7 +9,8 @@ use serde::{Deserialize, Serialize}; use crate::{ connector::utils::{ self, AddressDetailsData, BankDirectDebitBillingData, BrowserInformationData, - ConnectorCustomerData, PaymentsAuthorizeRequestData, RouterData, SetupMandateRequestData, + ConnectorCustomerData, PaymentsAuthorizeRequestData, PaymentsSetupMandateRequestData, + RouterData, }, core::errors, types::{ diff --git a/crates/router/src/connector/helcim.rs b/crates/router/src/connector/helcim.rs index a3cb5f7a22..87fcfdd36d 100644 --- a/crates/router/src/connector/helcim.rs +++ b/crates/router/src/connector/helcim.rs @@ -2,12 +2,15 @@ pub mod transformers; use std::fmt::Debug; +use diesel_models::enums; use error_stack::{IntoReport, ResultExt}; use masking::ExposeInterface; use transformers as helcim; +use super::utils::{to_connector_meta, PaymentsAuthorizeRequestData}; use crate::{ configs::settings, + consts::NO_ERROR_CODE, core::errors::{self, CustomResult}, headers, services::{ @@ -39,6 +42,16 @@ impl api::RefundExecute for Helcim {} impl api::RefundSync for Helcim {} impl api::PaymentToken for Helcim {} +impl Helcim { + pub fn connector_transaction_id( + &self, + connector_meta: &Option, + ) -> CustomResult, errors::ConnectorError> { + let meta: helcim::HelcimMetaData = to_connector_meta(connector_meta.clone())?; + Ok(Some(meta.preauth_transaction_id.to_string())) + } +} + impl ConnectorIntegration< api::PaymentMethodToken, @@ -60,12 +73,19 @@ where ) -> CustomResult)>, errors::ConnectorError> { let mut header = vec![( headers::CONTENT_TYPE.to_string(), - types::PaymentsAuthorizeType::get_content_type(self) - .to_string() - .into(), + self.get_content_type().to_string().into(), )]; let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + + //Helcim requires an Idempotency Key of length 25. We prefix every ID by "HS_". + const ID_LENGTH: usize = 22; + let mut idempotency_key = vec![( + headers::IDEMPOTENCY_KEY.to_string(), + utils::generate_id(ID_LENGTH, "HS").into_masked(), + )]; + header.append(&mut api_key); + header.append(&mut idempotency_key); Ok(header) } } @@ -75,6 +95,10 @@ impl ConnectorCommon for Helcim { "helcim" } + fn get_currency_unit(&self) -> api::CurrencyUnit { + api::CurrencyUnit::Base + } + fn common_get_content_type(&self) -> &'static str { "application/json" } @@ -90,7 +114,7 @@ impl ConnectorCommon for Helcim { let auth = helcim::HelcimAuthType::try_from(auth_type) .change_context(errors::ConnectorError::FailedToObtainAuthType)?; Ok(vec![( - headers::AUTHORIZATION.to_string(), + headers::API_TOKEN.to_string(), auth.api_key.expose().into_masked(), )]) } @@ -103,18 +127,33 @@ impl ConnectorCommon for Helcim { .response .parse_struct("HelcimErrorResponse") .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + let error_string = match response.errors { + transformers::HelcimErrorTypes::StringType(error) => error, + transformers::HelcimErrorTypes::JsonType(error) => error.to_string(), + }; Ok(ErrorResponse { status_code: res.status_code, - code: response.code, - message: response.message, - reason: response.reason, + code: NO_ERROR_CODE.to_owned(), + message: error_string.clone(), + reason: Some(error_string), }) } } impl ConnectorValidation for Helcim { - //TODO: implement functions when support enabled + fn validate_capture_method( + &self, + capture_method: Option, + ) -> CustomResult<(), errors::ConnectorError> { + let capture_method = capture_method.unwrap_or_default(); + match capture_method { + enums::CaptureMethod::Automatic | enums::CaptureMethod::Manual => Ok(()), + enums::CaptureMethod::ManualMultiple | enums::CaptureMethod::Scheduled => Err( + super::utils::construct_not_supported_error_report(capture_method, self.id()), + ), + } + } } impl ConnectorIntegration @@ -135,6 +174,70 @@ impl types::PaymentsResponseData, > for Helcim { + fn get_headers( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_url( + &self, + _req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}v2/payment/verify", self.base_url(connectors))) + } + fn get_request_body( + &self, + req: &types::SetupMandateRouterData, + ) -> CustomResult, errors::ConnectorError> { + let connector_req = helcim::HelcimVerifyRequest::try_from(req)?; + + let helcim_req = types::RequestBody::log_and_get_request_body( + &connector_req, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(helcim_req)) + } + fn build_request( + &self, + req: &types::SetupMandateRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::SetupMandateType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::SetupMandateType::get_headers(self, req, connectors)?) + .body(types::SetupMandateType::get_request_body(self, req)?) + .build(), + )) + } + fn handle_response( + &self, + data: &types::SetupMandateRouterData, + res: Response, + ) -> CustomResult { + let response: helcim::HelcimPaymentsResponse = res + .response + .parse_struct("Helcim PaymentsAuthorizeResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + } + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } } impl ConnectorIntegration @@ -154,17 +257,26 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + if req.request.is_auto_capture()? { + return Ok(format!("{}v2/payment/purchase", self.base_url(connectors))); + } + Ok(format!("{}v2/payment/preauth", self.base_url(connectors))) } fn get_request_body( &self, req: &types::PaymentsAuthorizeRouterData, ) -> CustomResult, errors::ConnectorError> { - let req_obj = helcim::HelcimPaymentsRequest::try_from(req)?; + let connector_router_data = helcim::HelcimRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount, + req, + ))?; + let req_obj = helcim::HelcimPaymentsRequest::try_from(&connector_router_data)?; let helcim_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -223,9 +335,17 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + types::PaymentsSyncType::get_content_type(self) + .to_string() + .into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -234,10 +354,19 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_payment_id = req + .request + .connector_transaction_id + .get_connector_transaction_id() + .change_context(errors::ConnectorError::MissingConnectorTransactionID)?; + + Ok(format!( + "{}v2/card-transactions/{connector_payment_id}", + self.base_url(connectors) + )) } fn build_request( @@ -277,6 +406,12 @@ impl ConnectorIntegration CustomResult { self.build_error_response(res) } + + // fn get_multiple_capture_sync_method( + // &self, + // ) -> CustomResult { + // Ok(services::CaptureSyncMethod::Individual) + // } } impl ConnectorIntegration @@ -297,16 +432,28 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}v2/payment/capture", self.base_url(connectors))) } fn get_request_body( &self, - _req: &types::PaymentsCaptureRouterData, + req: &types::PaymentsCaptureRouterData, ) -> CustomResult, errors::ConnectorError> { - Err(errors::ConnectorError::NotImplemented("get_request_body method".to_string()).into()) + let connector_router_data = helcim::HelcimRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.amount_to_capture, + req, + ))?; + let connector_req = helcim::HelcimCaptureRequest::try_from(&connector_router_data)?; + let helcim_req = types::RequestBody::log_and_get_request_body( + &connector_req, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(helcim_req)) } fn build_request( @@ -354,6 +501,78 @@ impl ConnectorIntegration for Helcim { + fn get_headers( + &self, + req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult)>, errors::ConnectorError> { + self.build_headers(req, connectors) + } + + fn get_content_type(&self) -> &'static str { + self.common_get_content_type() + } + + fn get_url( + &self, + _req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult { + Ok(format!("{}v2/payment/reverse", self.base_url(connectors))) + } + + fn get_request_body( + &self, + req: &types::PaymentsCancelRouterData, + ) -> CustomResult, errors::ConnectorError> { + let req_obj = helcim::HelcimVoidRequest::try_from(req)?; + let helcim_req = types::RequestBody::log_and_get_request_body( + &req_obj, + utils::Encode::::encode_to_string_of_json, + ) + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + Ok(Some(helcim_req)) + } + + fn build_request( + &self, + req: &types::PaymentsCancelRouterData, + connectors: &settings::Connectors, + ) -> CustomResult, errors::ConnectorError> { + Ok(Some( + services::RequestBuilder::new() + .method(services::Method::Post) + .url(&types::PaymentsVoidType::get_url(self, req, connectors)?) + .attach_default_headers() + .headers(types::PaymentsVoidType::get_headers(self, req, connectors)?) + .body(types::PaymentsVoidType::get_request_body(self, req)?) + .build(), + )) + } + + fn handle_response( + &self, + data: &types::PaymentsCancelRouterData, + res: Response, + ) -> CustomResult { + let response: helcim::HelcimPaymentsResponse = res + .response + .parse_struct("HelcimPaymentsResponse") + .change_context(errors::ConnectorError::ResponseDeserializationFailed)?; + types::RouterData::try_from(types::ResponseRouterData { + response, + data: data.clone(), + http_code: res.status_code, + }) + .change_context(errors::ConnectorError::ResponseHandlingFailed) + } + + fn get_error_response( + &self, + res: Response, + ) -> CustomResult { + self.build_error_response(res) + } } impl ConnectorIntegration for Helcim { @@ -372,16 +591,22 @@ impl ConnectorIntegration, - _connectors: &settings::Connectors, + connectors: &settings::Connectors, ) -> CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + Ok(format!("{}v2/payment/refund", self.base_url(connectors))) } fn get_request_body( &self, req: &types::RefundsRouterData, ) -> CustomResult, errors::ConnectorError> { - let req_obj = helcim::HelcimRefundRequest::try_from(req)?; + let connector_router_data = helcim::HelcimRouterData::try_from(( + &self.get_currency_unit(), + req.request.currency, + req.request.refund_amount, + req, + ))?; + let req_obj = helcim::HelcimRefundRequest::try_from(&connector_router_data)?; let helcim_req = types::RequestBody::log_and_get_request_body( &req_obj, utils::Encode::::encode_to_string_of_json, @@ -435,9 +660,17 @@ impl ConnectorIntegration CustomResult)>, errors::ConnectorError> { - self.build_headers(req, connectors) + let mut header = vec![( + headers::CONTENT_TYPE.to_string(), + types::RefundSyncType::get_content_type(self) + .to_string() + .into(), + )]; + let mut api_key = self.get_auth_header(&req.connector_auth_type)?; + header.append(&mut api_key); + Ok(header) } fn get_content_type(&self) -> &'static str { @@ -446,10 +679,19 @@ impl ConnectorIntegration CustomResult { - Err(errors::ConnectorError::NotImplemented("get_url method".to_string()).into()) + let connector_refund_id = req + .request + .connector_refund_id + .clone() + .ok_or(errors::ConnectorError::MissingConnectorRefundID)?; + + Ok(format!( + "{}v2/card-transactions/{connector_refund_id}", + self.base_url(connectors) + )) } fn build_request( diff --git a/crates/router/src/connector/helcim/transformers.rs b/crates/router/src/connector/helcim/transformers.rs index 20a8bef08a..9510ff6e67 100644 --- a/crates/router/src/connector/helcim/transformers.rs +++ b/crates/router/src/connector/helcim/transformers.rs @@ -1,53 +1,238 @@ +use common_utils::pii::{Email, IpAddress}; +use error_stack::{IntoReport, ResultExt}; use masking::Secret; use serde::{Deserialize, Serialize}; use crate::{ - connector::utils::PaymentsAuthorizeRequestData, + connector::utils::{ + self, AddressDetailsData, BrowserInformationData, CardData, PaymentsAuthorizeRequestData, + PaymentsCancelRequestData, PaymentsCaptureRequestData, PaymentsSetupMandateRequestData, + RefundsRequestData, RouterData, + }, core::errors, types::{self, api, storage::enums}, }; -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct HelcimPaymentsRequest { - amount: i64, - card: HelcimCard, +#[derive(Debug, Serialize)] +pub struct HelcimRouterData { + pub amount: f64, + pub router_data: T, } -#[derive(Default, Debug, Serialize, Eq, PartialEq)] -pub struct HelcimCard { - name: Secret, - number: cards::CardNumber, - expiry_month: Secret, - expiry_year: Secret, - cvc: Secret, - complete: bool, -} - -impl TryFrom<&types::PaymentsAuthorizeRouterData> for HelcimPaymentsRequest { +impl + TryFrom<( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + )> for HelcimRouterData +{ type Error = error_stack::Report; - fn try_from(item: &types::PaymentsAuthorizeRouterData) -> Result { + fn try_from( + (currency_unit, currency, amount, item): ( + &types::api::CurrencyUnit, + types::storage::enums::Currency, + i64, + T, + ), + ) -> Result { + let amount = utils::get_amount_as_f64(currency_unit, amount, currency)?; + Ok(Self { + amount, + router_data: item, + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimVerifyRequest { + currency: enums::Currency, + ip_address: Secret, + card_data: HelcimCard, + billing_address: HelcimBillingAddress, + #[serde(skip_serializing_if = "Option::is_none")] + ecommerce: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimPaymentsRequest { + amount: f64, + currency: enums::Currency, + ip_address: Secret, + card_data: HelcimCard, + billing_address: HelcimBillingAddress, + //The ecommerce field is an optional field in Connector Helcim. + //Setting the ecommerce field to true activates the Helcim Fraud Defender. + #[serde(skip_serializing_if = "Option::is_none")] + ecommerce: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimBillingAddress { + name: Secret, + street1: Secret, + postal_code: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + street2: Option>, + #[serde(skip_serializing_if = "Option::is_none")] + city: Option, + #[serde(skip_serializing_if = "Option::is_none")] + email: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimCard { + card_number: cards::CardNumber, + card_expiry: Secret, + card_c_v_v: Secret, +} + +impl TryFrom<(&types::SetupMandateRouterData, &api::Card)> for HelcimVerifyRequest { + type Error = error_stack::Report; + fn try_from(value: (&types::SetupMandateRouterData, &api::Card)) -> Result { + let (item, req_card) = value; + let card_data = HelcimCard { + card_expiry: req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + card_number: req_card.card_number.clone(), + card_c_v_v: req_card.card_cvc.clone(), + }; + let req_address = item.get_billing_address()?.to_owned(); + + let billing_address = HelcimBillingAddress { + name: req_address.get_full_name()?, + street1: req_address.get_line1()?.to_owned(), + postal_code: req_address.get_zip()?.to_owned(), + street2: req_address.line2, + city: req_address.city, + email: item.request.email.clone(), + }; + let ip_address = item.request.get_browser_info()?.get_ip_address()?; + + Ok(Self { + currency: item.request.currency, + ip_address, + card_data, + billing_address, + ecommerce: None, + }) + } +} + +impl TryFrom<&types::SetupMandateRouterData> for HelcimVerifyRequest { + type Error = error_stack::Report; + fn try_from(item: &types::SetupMandateRouterData) -> Result { match item.request.payment_method_data.clone() { - api::PaymentMethodData::Card(req_card) => { - let card = HelcimCard { - name: req_card.card_holder_name, - 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.request.is_auto_capture()?, - }; - Ok(Self { - amount: item.request.amount, - card, - }) + api::PaymentMethodData::Card(req_card) => Self::try_from((item, &req_card)), + api_models::payments::PaymentMethodData::BankTransfer(_) => Err( + errors::ConnectorError::NotImplemented("Payment Method".to_string()), + ) + .into_report(), + api_models::payments::PaymentMethodData::CardRedirect(_) + | api_models::payments::PaymentMethodData::Wallet(_) + | api_models::payments::PaymentMethodData::PayLater(_) + | api_models::payments::PaymentMethodData::BankRedirect(_) + | api_models::payments::PaymentMethodData::BankDebit(_) + | api_models::payments::PaymentMethodData::Crypto(_) + | api_models::payments::PaymentMethodData::MandatePayment + | api_models::payments::PaymentMethodData::Reward + | api_models::payments::PaymentMethodData::Upi(_) + | api_models::payments::PaymentMethodData::Voucher(_) + | api_models::payments::PaymentMethodData::GiftCard(_) => { + Err(errors::ConnectorError::NotSupported { + message: format!("{:?}", item.request.payment_method_data), + connector: "Helcim", + })? + } + } + } +} + +impl + TryFrom<( + &HelcimRouterData<&types::PaymentsAuthorizeRouterData>, + &api::Card, + )> for HelcimPaymentsRequest +{ + type Error = error_stack::Report; + fn try_from( + value: ( + &HelcimRouterData<&types::PaymentsAuthorizeRouterData>, + &api::Card, + ), + ) -> Result { + let (item, req_card) = value; + let card_data = HelcimCard { + card_expiry: req_card.get_card_expiry_month_year_2_digit_with_delimiter("".to_string()), + card_number: req_card.card_number.clone(), + card_c_v_v: req_card.card_cvc.clone(), + }; + let req_address = item + .router_data + .get_billing()? + .to_owned() + .address + .ok_or_else(utils::missing_field_err("billing.address"))?; + + let billing_address = HelcimBillingAddress { + name: req_address.get_full_name()?, + street1: req_address.get_line1()?.to_owned(), + postal_code: req_address.get_zip()?.to_owned(), + street2: req_address.line2, + city: req_address.city, + email: item.router_data.request.email.clone(), + }; + + let ip_address = item + .router_data + .request + .get_browser_info()? + .get_ip_address()?; + Ok(Self { + amount: item.amount.to_owned(), + currency: item.router_data.request.currency, + ip_address, + card_data, + billing_address, + ecommerce: None, + }) + } +} + +impl TryFrom<&HelcimRouterData<&types::PaymentsAuthorizeRouterData>> for HelcimPaymentsRequest { + type Error = error_stack::Report; + fn try_from( + item: &HelcimRouterData<&types::PaymentsAuthorizeRouterData>, + ) -> Result { + match item.router_data.request.payment_method_data.clone() { + api::PaymentMethodData::Card(req_card) => Self::try_from((item, &req_card)), + api_models::payments::PaymentMethodData::BankTransfer(_) => Err( + errors::ConnectorError::NotImplemented("Payment Method".to_string()), + ) + .into_report(), + api_models::payments::PaymentMethodData::CardRedirect(_) + | api_models::payments::PaymentMethodData::Wallet(_) + | api_models::payments::PaymentMethodData::PayLater(_) + | api_models::payments::PaymentMethodData::BankRedirect(_) + | api_models::payments::PaymentMethodData::BankDebit(_) + | api_models::payments::PaymentMethodData::Crypto(_) + | api_models::payments::PaymentMethodData::MandatePayment + | api_models::payments::PaymentMethodData::Reward + | api_models::payments::PaymentMethodData::Upi(_) + | api_models::payments::PaymentMethodData::Voucher(_) + | api_models::payments::PaymentMethodData::GiftCard(_) => { + Err(errors::ConnectorError::NotSupported { + message: format!("{:?}", item.router_data.request.payment_method_data), + connector: "Helcim", + })? } - _ => Err(errors::ConnectorError::NotImplemented("Payment methods".to_string()).into()), } } } -//TODO: Fill the struct with respective fields // Auth Struct pub struct HelcimAuthType { pub(super) api_key: Secret, @@ -65,100 +250,413 @@ impl TryFrom<&types::ConnectorAuthType> for HelcimAuthType { } } // PaymentsResponse -//TODO: Append the remaining status flags -#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)] -#[serde(rename_all = "lowercase")] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "UPPERCASE")] pub enum HelcimPaymentStatus { - Succeeded, - Failed, - #[default] - Processing, + Approved, + Declined, } -impl From for enums::AttemptStatus { - fn from(item: HelcimPaymentStatus) -> Self { - match item { - HelcimPaymentStatus::Succeeded => Self::Charged, - HelcimPaymentStatus::Failed => Self::Failure, - HelcimPaymentStatus::Processing => Self::Authorizing, +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HelcimTransactionType { + Purchase, + PreAuth, + Capture, + Verify, + Reverse, +} + +impl From for enums::AttemptStatus { + fn from(item: HelcimPaymentsResponse) -> Self { + match item.transaction_type { + HelcimTransactionType::Purchase | HelcimTransactionType::Verify => match item.status { + HelcimPaymentStatus::Approved => Self::Charged, + HelcimPaymentStatus::Declined => Self::Failure, + }, + HelcimTransactionType::PreAuth => match item.status { + HelcimPaymentStatus::Approved => Self::Authorized, + HelcimPaymentStatus::Declined => Self::AuthorizationFailed, + }, + HelcimTransactionType::Capture => match item.status { + HelcimPaymentStatus::Approved => Self::Charged, + HelcimPaymentStatus::Declined => Self::CaptureFailed, + }, + HelcimTransactionType::Reverse => match item.status { + HelcimPaymentStatus::Approved => Self::Voided, + HelcimPaymentStatus::Declined => Self::VoidFailed, + }, } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize, PartialEq)] +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct HelcimPaymentsResponse { status: HelcimPaymentStatus, - id: String, + transaction_id: u64, + #[serde(rename = "type")] + transaction_type: HelcimTransactionType, } -impl - TryFrom> - for types::RouterData +impl + TryFrom< + types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::SetupMandateRequestData, + types::PaymentsResponseData, + >, + > for types::RouterData { type Error = error_stack::Report; fn try_from( - item: types::ResponseRouterData, + item: types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::SetupMandateRequestData, + 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.transaction_id.to_string(), + ), redirection_data: None, mandate_reference: None, connector_metadata: None, network_txn_id: None, connector_response_reference_id: None, }), + status: enums::AttemptStatus::from(item.response), ..item.data }) } } -//TODO: Fill the struct with respective fields -// REFUND : -// Type definition for RefundRequest -#[derive(Default, Debug, Serialize)] -pub struct HelcimRefundRequest { - pub amount: i64, +#[derive(Debug, Deserialize, Serialize)] +pub struct HelcimMetaData { + pub preauth_transaction_id: u64, } -impl TryFrom<&types::RefundsRouterData> for HelcimRefundRequest { +impl + TryFrom< + types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ type Error = error_stack::Report; - fn try_from(item: &types::RefundsRouterData) -> Result { + fn try_from( + item: types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsAuthorizeData, + types::PaymentsResponseData, + >, + ) -> Result { + //PreAuth Transaction ID is stored in connector metadata + //Initially resource_id is stored as NoResponseID for manual capture + //After Capture Transaction is completed it is updated to store the Capture ID + let resource_id = if item.data.request.is_auto_capture()? { + types::ResponseId::ConnectorTransactionId(item.response.transaction_id.to_string()) + } else { + types::ResponseId::NoResponseId + }; + let connector_metadata = if !item.data.request.is_auto_capture()? { + Some(serde_json::json!(HelcimMetaData { + preauth_transaction_id: item.response.transaction_id, + })) + } else { + None + }; Ok(Self { - amount: item.request.refund_amount, + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id, + redirection_data: None, + mandate_reference: None, + connector_metadata, + network_txn_id: None, + connector_response_reference_id: None, + }), + status: enums::AttemptStatus::from(item.response), + ..item.data }) } } -// Type definition for Refund Response +// impl utils::MultipleCaptureSyncResponse for HelcimPaymentsResponse { +// fn get_connector_capture_id(&self) -> String { +// self.transaction_id.to_string() +// } -#[allow(dead_code)] -#[derive(Debug, Serialize, Default, Deserialize, Clone)] -pub enum RefundStatus { - Succeeded, - Failed, - #[default] - Processing, -} +// fn get_capture_attempt_status(&self) -> diesel_models::enums::AttemptStatus { +// enums::AttemptStatus::from(self.to_owned()) +// } -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 +// fn is_capture_response(&self) -> bool { +// true +// } + +// fn get_amount_captured(&self) -> Option { +// Some(self.amount) +// } +// fn get_connector_reference_id(&self) -> Option { +// None +// } +// } + +impl + TryFrom< + types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsSyncData, + types::PaymentsResponseData, + >, + ) -> Result { + match item.data.request.sync_type { + types::SyncRequestType::SinglePaymentSync => Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.transaction_id.to_string(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + status: enums::AttemptStatus::from(item.response), + ..item.data + }), + types::SyncRequestType::MultipleCaptureSync(_) => { + Err(errors::ConnectorError::NotImplemented( + "manual multiple capture sync".to_string(), + ) + .into()) + // let capture_sync_response_list = + // utils::construct_captures_response_hashmap(vec![item.response]); + // Ok(Self { + // response: Ok(types::PaymentsResponseData::MultipleCaptureResponse { + // capture_sync_response_list, + // }), + // ..item.data + // }) + } } } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Clone, Serialize, Deserialize)] +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimCaptureRequest { + pre_auth_transaction_id: u64, + amount: f64, + ip_address: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + ecommerce: Option, +} + +impl TryFrom<&HelcimRouterData<&types::PaymentsCaptureRouterData>> for HelcimCaptureRequest { + type Error = error_stack::Report; + fn try_from( + item: &HelcimRouterData<&types::PaymentsCaptureRouterData>, + ) -> Result { + let ip_address = item + .router_data + .request + .get_browser_info()? + .get_ip_address()?; + Ok(Self { + pre_auth_transaction_id: item + .router_data + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + amount: item.amount, + ip_address, + ecommerce: None, + }) + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsCaptureData, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.transaction_id.to_string(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + status: enums::AttemptStatus::from(item.response), + ..item.data + }) + } +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimVoidRequest { + card_transaction_id: u64, + ip_address: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + ecommerce: Option, +} + +impl TryFrom<&types::PaymentsCancelRouterData> for HelcimVoidRequest { + type Error = error_stack::Report; + fn try_from(item: &types::PaymentsCancelRouterData) -> Result { + let ip_address = item.request.get_browser_info()?.get_ip_address()?; + Ok(Self { + card_transaction_id: item + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?, + ip_address, + ecommerce: None, + }) + } +} + +impl + TryFrom< + types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + > for types::RouterData +{ + type Error = error_stack::Report; + fn try_from( + item: types::ResponseRouterData< + F, + HelcimPaymentsResponse, + types::PaymentsCancelData, + types::PaymentsResponseData, + >, + ) -> Result { + Ok(Self { + response: Ok(types::PaymentsResponseData::TransactionResponse { + resource_id: types::ResponseId::ConnectorTransactionId( + item.response.transaction_id.to_string(), + ), + redirection_data: None, + mandate_reference: None, + connector_metadata: None, + network_txn_id: None, + connector_response_reference_id: None, + }), + status: enums::AttemptStatus::from(item.response), + ..item.data + }) + } +} + +// REFUND : +// Type definition for RefundRequest +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct HelcimRefundRequest { + amount: f64, + original_transaction_id: u64, + ip_address: Secret, + #[serde(skip_serializing_if = "Option::is_none")] + ecommerce: Option, +} + +impl TryFrom<&HelcimRouterData<&types::RefundsRouterData>> for HelcimRefundRequest { + type Error = error_stack::Report; + fn try_from( + item: &HelcimRouterData<&types::RefundsRouterData>, + ) -> Result { + let original_transaction_id = item + .router_data + .request + .connector_transaction_id + .parse::() + .into_report() + .change_context(errors::ConnectorError::RequestEncodingFailed)?; + + let ip_address = item + .router_data + .request + .get_browser_info()? + .get_ip_address()?; + Ok(Self { + amount: item.amount, + original_transaction_id, + ip_address, + ecommerce: None, + }) + } +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum HelcimRefundTransactionType { + Refund, +} +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct RefundResponse { - id: String, - status: RefundStatus, + status: HelcimPaymentStatus, + transaction_id: u64, + #[serde(rename = "type")] + transaction_type: HelcimRefundTransactionType, +} + +impl From for enums::RefundStatus { + fn from(item: RefundResponse) -> Self { + match item.transaction_type { + HelcimRefundTransactionType::Refund => match item.status { + HelcimPaymentStatus::Approved => Self::Success, + HelcimPaymentStatus::Declined => Self::Failure, + }, + } + } } impl TryFrom> @@ -170,8 +668,8 @@ impl TryFrom> ) -> Result { Ok(Self { response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.transaction_id.to_string(), + refund_status: enums::RefundStatus::from(item.response), }), ..item.data }) @@ -187,19 +685,22 @@ impl TryFrom> ) -> Result { Ok(Self { response: Ok(types::RefundsResponseData { - connector_refund_id: item.response.id.to_string(), - refund_status: enums::RefundStatus::from(item.response.status), + connector_refund_id: item.response.transaction_id.to_string(), + refund_status: enums::RefundStatus::from(item.response), }), ..item.data }) } } -//TODO: Fill the struct with respective fields -#[derive(Default, Debug, Serialize, Deserialize, PartialEq)] -pub struct HelcimErrorResponse { - pub status_code: u16, - pub code: String, - pub message: String, - pub reason: Option, +#[derive(Debug, strum::Display, Deserialize)] +#[serde(untagged)] +pub enum HelcimErrorTypes { + StringType(String), + JsonType(serde_json::Value), +} + +#[derive(Debug, Deserialize)] +pub struct HelcimErrorResponse { + pub errors: HelcimErrorTypes, } diff --git a/crates/router/src/connector/utils.rs b/crates/router/src/connector/utils.rs index 455646f619..3a8cae3a63 100644 --- a/crates/router/src/connector/utils.rs +++ b/crates/router/src/connector/utils.rs @@ -248,19 +248,25 @@ impl PaymentsPreProcessingData for types::PaymentsPreProcessingData { pub trait PaymentsCaptureRequestData { fn is_multiple_capture(&self) -> bool; + fn get_browser_info(&self) -> Result; } impl PaymentsCaptureRequestData for types::PaymentsCaptureData { fn is_multiple_capture(&self) -> bool { self.multiple_capture_data.is_some() } + fn get_browser_info(&self) -> Result { + self.browser_info + .clone() + .ok_or_else(missing_field_err("browser_info")) + } } -pub trait SetupMandateRequestData { +pub trait PaymentsSetupMandateRequestData { fn get_browser_info(&self) -> Result; } -impl SetupMandateRequestData for types::SetupMandateRequestData { +impl PaymentsSetupMandateRequestData for types::SetupMandateRequestData { fn get_browser_info(&self) -> Result { self.browser_info .clone() @@ -511,6 +517,7 @@ pub trait PaymentsCancelRequestData { fn get_amount(&self) -> Result; fn get_currency(&self) -> Result; fn get_cancellation_reason(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl PaymentsCancelRequestData for PaymentsCancelData { @@ -525,11 +532,17 @@ impl PaymentsCancelRequestData for PaymentsCancelData { .clone() .ok_or_else(missing_field_err("cancellation_reason")) } + fn get_browser_info(&self) -> Result { + self.browser_info + .clone() + .ok_or_else(missing_field_err("browser_info")) + } } pub trait RefundsRequestData { fn get_connector_refund_id(&self) -> Result; fn get_webhook_url(&self) -> Result; + fn get_browser_info(&self) -> Result; } impl RefundsRequestData for types::RefundsData { @@ -545,6 +558,11 @@ impl RefundsRequestData for types::RefundsData { .clone() .ok_or_else(missing_field_err("webhook_url")) } + fn get_browser_info(&self) -> Result { + self.browser_info + .clone() + .ok_or_else(missing_field_err("browser_info")) + } } #[derive(Clone, Debug, serde::Serialize)] diff --git a/crates/router/src/core/admin.rs b/crates/router/src/core/admin.rs index 7da0b73f74..2fecb57323 100644 --- a/crates/router/src/core/admin.rs +++ b/crates/router/src/core/admin.rs @@ -1365,6 +1365,10 @@ pub(crate) fn validate_auth_and_metadata_type( gocardless::transformers::GocardlessAuthType::try_from(val)?; Ok(()) } + api_enums::Connector::Helcim => { + helcim::transformers::HelcimAuthType::try_from(val)?; + Ok(()) + } api_enums::Connector::Iatapay => { iatapay::transformers::IatapayAuthType::try_from(val)?; Ok(()) diff --git a/crates/router/src/core/payments/flows.rs b/crates/router/src/core/payments/flows.rs index b54f1c6cd5..3c42049bc4 100644 --- a/crates/router/src/core/payments/flows.rs +++ b/crates/router/src/core/payments/flows.rs @@ -427,7 +427,6 @@ default_imp_for_connector_request_id!( connector::Globalpay, connector::Globepay, connector::Gocardless, - connector::Helcim, connector::Iatapay, connector::Klarna, connector::Mollie, diff --git a/crates/router/src/core/payments/transformers.rs b/crates/router/src/core/payments/transformers.rs index ac15401a33..23fbf1ea42 100644 --- a/crates/router/src/core/payments/transformers.rs +++ b/crates/router/src/core/payments/transformers.rs @@ -9,7 +9,7 @@ use router_env::{instrument, tracing}; use super::{flows::Feature, PaymentData}; use crate::{ configs::settings::{ConnectorRequestReferenceIdConfig, Server}, - connector::Nexinets, + connector::{Helcim, Nexinets}, core::{ errors::{self, RouterResponse, RouterResult}, payments::{self, helpers}, @@ -1059,6 +1059,21 @@ impl TryFrom> for types::PaymentsSyncData } } +impl api::ConnectorTransactionId for Helcim { + fn connector_transaction_id( + &self, + payment_attempt: storage::PaymentAttempt, + ) -> Result, errors::ApiErrorResponse> { + if payment_attempt.connector_transaction_id.is_none() { + let metadata = + Self::connector_transaction_id(self, &payment_attempt.connector_metadata); + metadata.map_err(|_| errors::ApiErrorResponse::ResourceIdNotFound) + } else { + Ok(payment_attempt.connector_transaction_id) + } + } +} + impl api::ConnectorTransactionId for Nexinets { fn connector_transaction_id( &self, @@ -1083,6 +1098,16 @@ impl TryFrom> for types::PaymentsCaptureD .payment_attempt .amount_to_capture .map_or(payment_data.amount.into(), |capture_amount| capture_amount); + 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", + })?; + Ok(Self { amount_to_capture, currency: payment_data.currency, @@ -1102,6 +1127,7 @@ impl TryFrom> for types::PaymentsCaptureD }), None => None, }, + browser_info, }) } } @@ -1116,6 +1142,15 @@ impl TryFrom> for types::PaymentsCancelDa &additional_data.connector_name, api::GetToken::Connector, )?; + 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", + })?; Ok(Self { amount: Some(payment_data.amount.into()), currency: Some(payment_data.currency), @@ -1125,6 +1160,7 @@ impl TryFrom> for types::PaymentsCancelDa .ok_or(errors::ApiErrorResponse::ResourceIdNotFound)?, cancellation_reason: payment_data.payment_attempt.cancellation_reason, connector_meta: payment_data.payment_attempt.connector_metadata, + browser_info, }) } } diff --git a/crates/router/src/core/utils.rs b/crates/router/src/core/utils.rs index 7790e2ac5b..beedfb98bc 100644 --- a/crates/router/src/core/utils.rs +++ b/crates/router/src/core/utils.rs @@ -269,6 +269,15 @@ pub async fn construct_refund_router_data<'a, F>( None }; + let browser_info: Option = payment_attempt + .browser_info + .clone() + .map(|b| b.parse_value("BrowserInformation")) + .transpose() + .change_context(errors::ApiErrorResponse::InvalidDataValue { + field_name: "browser_info", + })?; + let router_data = types::RouterData { flow: PhantomData, merchant_id: merchant_account.merchant_id.clone(), @@ -297,6 +306,7 @@ pub async fn construct_refund_router_data<'a, F>( connector_metadata: payment_attempt.connector_metadata.clone(), reason: refund.refund_reason.clone(), connector_refund_id: refund.connector_refund_id.clone(), + browser_info, }, response: Ok(types::RefundsResponseData { diff --git a/crates/router/src/lib.rs b/crates/router/src/lib.rs index 008991bb45..5a9e246032 100644 --- a/crates/router/src/lib.rs +++ b/crates/router/src/lib.rs @@ -47,9 +47,11 @@ pub mod headers { pub const API_KEY: &str = "API-KEY"; pub const APIKEY: &str = "apikey"; pub const X_CC_API_KEY: &str = "X-CC-Api-Key"; + pub const API_TOKEN: &str = "Api-Token"; pub const AUTHORIZATION: &str = "Authorization"; pub const CONTENT_TYPE: &str = "Content-Type"; pub const DATE: &str = "Date"; + pub const IDEMPOTENCY_KEY: &str = "Idempotency-Key"; pub const NONCE: &str = "nonce"; pub const TIMESTAMP: &str = "Timestamp"; pub const TOKEN: &str = "token"; diff --git a/crates/router/src/types.rs b/crates/router/src/types.rs index 48e3697c6b..298b83faf1 100644 --- a/crates/router/src/types.rs +++ b/crates/router/src/types.rs @@ -390,6 +390,7 @@ pub struct PaymentsCaptureData { pub payment_amount: i64, pub multiple_capture_data: Option, pub connector_meta: Option, + pub browser_info: Option, } #[allow(dead_code)] @@ -492,6 +493,7 @@ pub struct PaymentsCancelData { pub connector_transaction_id: String, pub cancellation_reason: Option, pub connector_meta: Option, + pub browser_info: Option, } #[derive(Debug, Default, Clone)] @@ -711,6 +713,7 @@ pub struct RefundsData { pub refund_amount: i64, /// Arbitrary metadata required for refund pub connector_metadata: Option, + pub browser_info: Option, } #[derive(Clone, Debug, Default, serde::Serialize, serde::Deserialize)] diff --git a/crates/router/src/types/api.rs b/crates/router/src/types/api.rs index 6c1790d848..e56285d05d 100644 --- a/crates/router/src/types/api.rs +++ b/crates/router/src/types/api.rs @@ -330,7 +330,7 @@ impl ConnectorData { enums::Connector::Globalpay => Ok(Box::new(&connector::Globalpay)), enums::Connector::Globepay => Ok(Box::new(&connector::Globepay)), enums::Connector::Gocardless => Ok(Box::new(&connector::Gocardless)), - //enums::Connector::Helcim => Ok(Box::new(&connector::Helcim)), , it is added as template code for future usage + enums::Connector::Helcim => Ok(Box::new(&connector::Helcim)), enums::Connector::Iatapay => Ok(Box::new(&connector::Iatapay)), enums::Connector::Klarna => Ok(Box::new(&connector::Klarna)), enums::Connector::Mollie => Ok(Box::new(&connector::Mollie)), diff --git a/crates/router/tests/connectors/aci.rs b/crates/router/tests/connectors/aci.rs index cba0640e79..4fe36f3687 100644 --- a/crates/router/tests/connectors/aci.rs +++ b/crates/router/tests/connectors/aci.rs @@ -126,6 +126,7 @@ fn construct_refund_router_data() -> types::RefundsRouterData { connector_metadata: None, reason: None, connector_refund_id: None, + browser_info: None, }, payment_method_id: None, response: Err(types::ErrorResponse::default()), diff --git a/crates/router/tests/connectors/utils.rs b/crates/router/tests/connectors/utils.rs index 7d600d98d3..1cb3b48f72 100644 --- a/crates/router/tests/connectors/utils.rs +++ b/crates/router/tests/connectors/utils.rs @@ -388,6 +388,7 @@ pub trait ConnectorActions: Connector { connector_metadata: None, reason: None, connector_refund_id: Some(refund_id), + browser_info: None, }), payment_info, ); @@ -955,6 +956,7 @@ impl Default for PaymentRefundType { connector_metadata: None, reason: Some("Customer returned product".to_string()), connector_refund_id: None, + browser_info: None, }; Self(data) } diff --git a/openapi/openapi_spec.json b/openapi/openapi_spec.json index 3f1f168033..d46f55efb0 100644 --- a/openapi/openapi_spec.json +++ b/openapi/openapi_spec.json @@ -3940,6 +3940,7 @@ "globalpay", "globepay", "gocardless", + "helcim", "iatapay", "klarna", "mollie",